Szereg poleceń sed działa w wierszu poleceń, ale nie w skrypcie

9

Pracuję z danymi .csvwyjściowymi tego zapytania danych SE, które wygląda tak (tylko z 5022 wpisami):

"{
  ""id"": 281952,
  ""title"": ""Flash 11.2 No Longer Supported by Google Play""
}"
"{
  ""id"": 281993,
  ""title"": ""Netbeans won't open in Ubuntu""
}"

(I ma ^Mkońcówki linii między [liczba], a „„ tytuł ””). Potrzebuję go wyglądać tak:

281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

Naprawiłem to w pewnym edytorze tekstu, który z łatwością pozostanie bezimienny, ale chciałem stworzyć skrypt, aby nie musiałem tego robić za każdym razem, gdy zapytanie jest odświeżane, aby inni mogli go używać. Użyłem sed...

Ta seria poleceń działa idealnie (chociaż może być nieefektywna; jest to tylko rozwiązanie prób i błędów):

# Print the ^M and remove them, write to a new file:
cat -v QueryR* | sed 's/\^M//' > QueryNew
# remove all the other junk:
sed -i 's/{//' QueryNew
sed -i 's/}//' QueryNew
sed -i 's/""//g' QueryNew
sed -i 's/^"//' QueryNew
sed -i '/,/{N;/\n.*title:\s/{s/,\n.*title:\s/,\ /}}' QueryNew
sed -i 's/^\s\+//' QueryNew
sed -i '/^\s*$/d' QueryNew
sed -i 's/^id:\ //' QueryNew
sed -i 's/,\ /,/' QueryNew
sed -i 's/\\//g' QueryNew

Dlaczego tak nie jest? Tylko ^Mi {}zostaną usunięte, a wszystko inne nadal tam jest.

#!/bin/bash
cat -v QueryR* | sed 's/\^M//' > QueryNew
sed -i '{
       s/{//
       s/}//
       s/""//g
       s/^"//
       /,/{N;/\n.*title:\s/{s/,\n.*title:\s/,\ /}}
       s/^\s\+//
       /^\s*$/d
       s/^id:\ //
       s/,\ /,/
       s/\\//g
}' QueryNew

Jestem pewien, że mój błąd jest naprawdę oczywisty ...

Zanna
źródło

Odpowiedzi:

11

Korzystanie cat -vwłączyć znaki CR na dosłowne ^Msekwencji wydaje się zasadniczo brzydkie mi - jeśli trzeba usunąć zakończenia linii DOS, stosowanie dos2unix, trlub sed 's/\r$//'

Jeśli nalegasz na użycie sed, sugeruję wydrukowanie bitów, które chcesz, zamiast próbować usunąć wszystkie losowe bity, których nie chcesz - na przykład

$ sed -rn -e 's/\"//g' -e 's/(.*): (.*)\r/\2/p' QueryR | paste -d '' - -
281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

Możesz uzyskać upodobanie i przenieść usuwanie wycen do ekstrakcji klucz-wartość, dopasowując zero lub więcej cytatów na każdym końcu sekwencji wartości

$ sed -rn 's/(.*): \"*([^"]*)\"*\r/\2/p' QueryR | paste -d '' - -
281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

Można dostać naprawdę wyszukane i naśladować pastew sednajpierw łączenia par linii na ,\r$zakończenie, a następnie dopasowanie par klucz-wartość pomnożyć ( g) i non-łapczywie

$ sed -rn '/,\r$/ {N; s/([^:]*): \"*([^:"]*)\"*\r\n?/\2/gp}' QueryR
281952,Flash 11.2 No Longer Supported by Google Play
281993,Netbeans won't open in Ubuntu

(Osobiście wolę podejście KISS i skorzystam z pierwszego).


FWIW, ponieważ wydaje się, że dane wejściowe są zawyżone JSON, sugeruję zainstalowanie odpowiedniego parsera JSON, takiego jak jq

sudo apt-get install jq

Możesz wtedy zrobić coś takiego

$ sed -e 's/["]["]/"/g' -e 's/"{/{/' -e 's/}"/}/' QueryR | jq '.id, .title' | paste -d, - -
281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"

który usuwa zbędne cudzysłowy, a następnie używa jqdo wyodrębnienia interesujących pól - zwróć uwagę, że jqwydaje się on obsługiwać zakończenia linii w stylu DOS, więc nie trzeba podejmować specjalnych kroków, aby je usunąć.

Zmień na, jq '.[]'aby zrzucić wszystkie pary atrybut-wartość.

Uznanie za inspirację i podstawową jqskładnię zaczerpniętą z Pokonywanie nowych linii za pomocą grep -o

steeldriver
źródło
1
ugh tak, idk dlaczego zapomniałem \r. jqzłamał się w pierwszym wierszu, w którym pole tytułowe miało dwukropek (pierwszy wiersz). Wciąż nie jestem pewien, dlaczego sedmnie nienawidzi, ale zabiłem niektóre z cytatów i \rna tej linii /,\r*/{N;/\n.*title.*:\s/{s/,\r*\n.*title.*:\s/,\ /}}i wreszcie działa jak ten . Wielkie dzięki ^ _ ^
Zanna
1
To DUŻO lepiej (ale nie chcę żadnego cytatu, więc sed -rn -e 's/\"\"//g' -e 's/^(.*): (.*)\r$/\2/p' QueryR* | paste -d '' - - zrobiłem to jak magia)
Zanna
5

Naprawiłem to dzięki steeldriver i dalszemu majsterkowaniu. Nierafinowane, ale działa.

sed  '{
       s/"{//
       s/}"//
       s/^"//
       /,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,/}}
       s/""//g
       s/^\s\+//
       /^\s*$/d
       s/^id:\ //
       s/\\//g
}' QueryR* | tee "$1"

tłumaczenie:
s/"{//Usuń "{
s/}"//Usuń }"
s/^"//Usuń "od początku
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}}dopasowania do linii ,\rw jednym wierszu i [whatever]title[whatever]:w następnym wierszu, zamień wszystko na ,
s/""//gUsuń wszystkie pozostałe podwójne podwójne cudzysłowy
s/^\s\+//Usuń białe spacje na początku linii
/^\s*$/dUsuń puste linie
s/^id:\ //Usuń id:i spację po nim
s/\\//gUsuń znaki odwrotne „dodane do niektórych pól tytułu)
tee "$1"określ na przykład plik wyjściowy podczas uruchamiania skryptu./queryclean newquery.csv

Zanna
źródło
4

Podczas gdy pytanie nasuwa sed, można obejść problemy związane z sed z Pythonem:

from __future__ import print_function
import sys

with open(sys.argv[1]) as f:
     for line in f:
         if '""id""' in line:
            print(line.strip().split(':')[1],end="")
         if '""title""' in line:
            title = " ".join(line.strip().split(':')[1:])
            print(title.replace('""'," "))

Ten kod jest zgodny zarówno z python2, jak i python3, więc oba będą działać

Przykładowy przebieg:

bash-4.3$ cat questions.txt 
"{
  ""id"": 281952,
  ""title"": ""Flash 11.2 No Longer Supported by Google Play""
}"
"{
  ""id"": 281993,
  ""title"": ""Netbeans won't open in Ubuntu""
}"
bash-4.3$ python3 parse_questions.py questions.txt 
 281952,  Flash 11.2 No Longer Supported by Google Play 
 281993,  Netbeans won't open in Ubuntu 
Sergiy Kolodyazhnyy
źródło
4

Trzy kolejne podejścia:

  1. awk

    $ awk -F'": ' '/\"id\"/{id=$NF;} 
                  /\"title\"/{
                    t=$NF; 
                    sub(/^""/,"",t); 
                    sub(/""$/,"",t); 
                    print id,t
                  }' OFS="" file 
    281952,Flash 11.2 No Longer Supported by Google Play
    281993,Netbeans won't open in Ubuntu
  2. Perl

    $ perl -lne '$id=$1 if /id"":\s*(\d+)/; 
                 if(/title"":\s*""(.*)""/){print "$id,$1"}' file 
    281952,Flash 11.2 No Longer Supported by Google Play
    281993,Netbeans won't open in Ubuntu
  3. GNU grep z wyrażeniami zgodnymi z Perlem i prostym perlem:

    $ grep -oP '(id"":\s*\K.*)|(title"":\s*""\K.*(?=""))' file | 
        perl -pe 'chomp if $.%2'
    281952,Flash 11.2 No Longer Supported by Google Play
    281993,Netbeans won't open in Ubuntu
terdon
źródło
4

To nie jest dokładnie odpowiedź na pytanie lub rozwiązanie problemu, ale aby pozbyć się niechcianych postaci, możesz użyć tr :

cat QueryR | tr -d '}{:"' 

a dostaniesz:

Wpisz opis zdjęcia tutaj

kcdtv
źródło
dzięki, muszę nauczyć się tr
obsługiwać
Nie jest tak potężny jak sed czy awk, ale jest bardzo prosty dla tego rodzaju rzeczy. Pozdrawiam :)
kcdtv 19.09.16
1

To kolejny skrypt napisany w Ruby. Zachowuje przecinki w tytule, które można łatwo importować do dowolnego programu arkusza kalkulacyjnego bez rozbijania kolumn.

csvfile = File.open('query-fixed.csv', 'w')

File.open('QueryResults2.csv') do |f|
    content = f.read
    content.gsub!(/\r\n?/, "\n")
    content.each_line do |line|
        id, title = '', ''
        if line.match('\"id\"')
            id = line.split(':')[1].strip[0..-2]
            csvfile.write(id + ',')
        end
        if line.match('\"title\"')
            title = line.partition(':')[2].scan(/"(.*)"/)[0][0]
            csvfile.write(title + "\n")
        end
    end
end

Po uruchomieniu programu wygenerowane dane wyjściowe będą wyglądały następująco

281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"
Anwar
źródło
To bardzo miłe :)
Zanna
Co powiesz na tytuły z :nimi zawarte?
Sнаđошƒаӽ
@ Sнаđошƒаӽ ups! Dzięki za wskaźnik. Naprawiono teraz!
Anwar