La serie di comandi sed funziona su riga di comando, ma non in uno script

9

Sto lavorando con l'output .csv di questa query di dati SE che assomiglia a questa (solo con 5022 voci):

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

(E ha la riga ^M desinenze tra [numero] e "" titolo ""). Ho bisogno che assomigli a questo:

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

L'ho risolto in un certo editor di testo che rimarrà senza nome abbastanza facilmente, ma volevo creare uno script in modo da non doverlo ripetere ogni volta che la query viene aggiornata e amp; così altri possono usarlo. Ho usato sed ...

Questa serie di comandi funziona perfettamente (anche se potrebbe essere inefficiente, è solo una soluzione trial-and-error):

# 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

Quindi, perché non questo? Solo ^M e {} vengono rimossi e tutto il resto è ancora lì.

#!/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

Sono sicuro che il mio errore sia davvero ovvio ...

    
posta Zanna 18.09.2016 - 00:37

6 risposte

11

Usare cat -v per trasformare i caratteri CR in letterali ^M di sequenze mi sembra fondamentalmente brutto - se devi rimuovere le terminazioni di riga DOS, usa dos2unix , tr o sed 's/\r$// '

Se insisti a usare sed, allora ti suggerisco di stampare i bit che fai vuoi, piuttosto che cercare di cancellare tutti i bit casuali che non hai - ad esempio

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

Potresti essere fantasioso e rotolare la rimozione delle quote nell'estrazione del valore-chiave abbinando zero o più virgolette a ciascuna estremità della sequenza di valori

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

Potresti ottenere veramente di fantasia ed emulare il paste in sed prima di unire coppie di linee sul finale ,\r$ e quindi fare corrispondere le coppie chiave-valore moltiplicare ( g ) e non avido

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

(Personalmente preferirei l'approccio KISS e usare il primo)

FWIW, dal momento che il tuo input sembra essere un JSON sovra-quotato, suggerirei di installare un parser JSON corretto come jq

sudo apt-get install jq

Puoi quindi fare qualcosa di simile

$ 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"

che rimuove le virgolette superflue e quindi usa jq per estrarre i campi di interesse - nota che jq sembra gestire le terminazioni di linea in stile DOS, quindi non è necessario prendere provvedimenti particolari per rimuoverle.

Passa a jq '.[]' per scaricare tutte le coppie valore-attributo.

Credito per ispirazione e sintassi di base jq tratto da Overcoming newlines con grep -o

    
risposta data steeldriver 18.09.2016 - 01:28
5

L'ho risolto grazie a steeldriver & amp; ulteriori ritocchi. Non raffinato ma funziona.

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"

traduzione:
s/"{// Rimuovi "{
s/}"// Rimuovi }"
s/^"// Rimuovi " dall'inizio della riga
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}} corrisponde ,\r a una riga e [whatever]title[whatever]: sulla riga successiva, sostituisci tutto con ,
s/""//g Rimuovi tutte le doppie virgolette doppie rimanenti
s/^\s\+// Rimuovi gli spazi bianchi dall'inizio delle righe
/^\s*$/d Rimuovi righe vuote
s/^id:\ // Rimuovi id: e spazio dopo di esso
s/\//g Rimuovi barre rovesciate (caratteri di escape per "aggiunti ad alcuni campi titolo)
tee "$1" specifica un file di uscita quando si esegue lo script, per esempio ./queryclean newquery.csv

    
risposta data Zanna 18.09.2016 - 09:47
4

Mentre la domanda chiede sed , si potrebbe risolvere i problemi di sed con Python:

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('""'," "))

Questo codice è compatibile sia con python2 che con python3, quindi funzionerà

Esecuzione di esempio:

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 
    
risposta data Sergiy Kolodyazhnyy 18.09.2016 - 13:43
4

Altri tre approcci:

  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 con regex compatibili perl e perl semplici:

    $ 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
    
risposta data terdon 18.09.2016 - 13:46
4

Questo non è esattamente rispondere alla tua domanda o risolvere il tuo problema, ma per sbarazzarti dei personaggi indesiderati puoi usare tr :

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

e otterrai:

    
risposta data kcdtv 18.09.2016 - 01:18
1

Questo è un altro script scritto in Ruby. Conserverà le virgole nel titolo, che possono essere facilmente importate in qualsiasi programma di fogli di calcolo senza rompere le colonne.

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

Dopo l'esecuzione del programma, l'output prodotto sarà simile a questi

281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"
    
risposta data Anwar 28.09.2016 - 20:35

Leggi altre domande sui tag