Serie di comandi sed funzionano sulla riga di comando, ma non in uno script


9

Sto lavorando con l' .csvoutput di questa query di dati SE che assomiglia a questo (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 ^Mterminazioni di riga 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

Ho risolto questo problema in un determinato editor di testo che rimarrà abbastanza facilmente senza nome, ma volevo creare uno script in modo da non doverlo rifare ogni volta che la query viene aggiornata e così altri possono usarla. Ho usato sed...

Questa serie di comandi funziona perfettamente (sebbene possa essere inefficiente; è solo una soluzione di prova ed errore):

# 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? Vengono rimossi solo ^Me {}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 è davvero evidente ...

Risposte:


11

Utilizzando cat -vper trasformare caratteri CR in letterali ^Msequenze sembra fondamentalmente brutto per me - se è necessario rimuovere fine riga DOS, l'uso dos2unix, tro sed 's/\r$//'

Se ti ostini a usare sed, allora ti suggerisco di stampare i bit non desiderati, piuttosto che cercare di eliminare tutti i bit casuali che non lo fanno - ad esempio

$ 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

Potresti avere fantasia e spostare la rimozione delle virgolette nell'estrazione del valore-chiave abbinando zero o più virgolette a ciascuna estremità della sequenza di valori

$ 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

Si potrebbe ottenere molto di fantasia e di emulare il pastein sedda prima di entrare coppie di linee sul ,\r$finale e quindi corrispondere le coppie chiave-valore si moltiplicano ( g) e non-avidamente

$ 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

(Personalmente preferirei l'approccio KISS e userò il primo).


FWIW, poiché il tuo input sembra essere JSON sopravvalutato, ti suggerirei di installare un parser JSON corretto come jq

sudo apt-get install jq

È quindi possibile 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 utilizza jqper estrarre i campi di interesse - nota che jqsembra gestire le terminazioni di linea in stile DOS, quindi non è necessario adottare misure speciali per rimuoverle.

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

Ringraziamo l'ispirazione e la jqsintassi di base tratte da Superare le nuove linee con grep -o


1
ugh sì, idk perché mi sono dimenticato \r. jqsi interruppe sulla prima riga dove il campo del titolo aveva due punti (la prima riga). Non sono ancora sicuro perché sedmi odia, ma ho ucciso alcune delle citazioni e \rin questa linea /,\r*/{N;/\n.*title.*:\s/{s/,\r*\n.*title.*:\s/,\ /}}e, infine, funziona come questo . Grazie mille ^ _ ^
Zanna,

1
È MOLTO meglio (ma non voglio nessuna delle virgolette così sed -rn -e 's/\"\"//g' -e 's/^(.*): (.*)\r$/\2/p' QueryR* | paste -d '' - - e fatto come per magia)
Zanna,

5

L'ho risolto grazie a steeldriver e ad ulteriori armeggi. 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
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}}corrispondenza di riga ,\rsu una riga e [whatever]title[whatever]:sulla riga successiva, sostituisci tutto ciò con ,
s/""//gRimuovi tutte le doppie virgolette doppie rimosse
s/^\s\+//Rimuovi spazio bianco dall'inizio delle righe
/^\s*$/dRimuovi righe vuote
s/^id:\ //Rimuovi id:e spazio dopo
s/\\//gRimuove barre rovesciate (caratteri di escape per "aggiunto ad alcuni campi del titolo)
tee "$1"specifica ad esempio un file durante l'esecuzione dello script./queryclean newquery.csv


4

Mentre la domanda si pone sed, si potrebbero aggirare 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 è conforme sia a python2 che a python3, quindi entrambi funzioneranno

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 

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 semplice:

    $ 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

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:

Inserisci qui la descrizione dell'immagine


grazie, devo imparare ad usare tr:)
Zanna,

Non è potente come sed o awk ma è molto semplice per quel tipo di cose. Saluti :)
kcdtv

1

Questa è un'altra sceneggiatura scritta in Ruby. Manterrà le virgole nel titolo, che possono essere facilmente importate in qualsiasi programma di foglio 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"

È molto bello :)
Zanna,

Che ne dici di titoli con :al loro interno?
Sнаđошƒаӽ,

@ Sнаđошƒаӽ oops! Grazie per il puntatore. Risolto ora!
Anwar,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.