Las series de comandos sed funcionan en la línea de comandos, pero no en un script

9

Estoy trabajando con la .csvsalida de esta consulta de datos SE que se ve así (solo con 5022 entradas):

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

(Y tiene ^Mterminaciones de línea entre [número] y "" título ""). Necesito que se vea así:

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

Lo arreglé en un editor de texto determinado que permanecerá sin nombre con bastante facilidad, pero quería crear un script para no tener que volver a hacerlo cada vez que se actualiza la consulta y para que otros puedan usarla. Yo solía sed...

Esta serie de comandos funciona perfectamente (aunque puede ser ineficiente; es solo una solución de prueba y 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

Entonces, ¿por qué no esto? Sólo el ^My {}se eliminan, y todo lo demás es todavía allí.

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

Estoy seguro de que mi error es realmente obvio ...

Zanna
fuente

Respuestas:

11

El uso cat -vde convertir caracteres CR en literales ^Msecuencias parece fundamentalmente feo para mí - si es necesario para eliminar los finales de línea de DOS, el uso dos2unix, tro sed 's/\r$//'

Si usted insiste en el uso de sed, entonces le sugiero que imprima los bits que no desea, en lugar de tratar de eliminar todos los bits aleatorios que no lo hacen - por ejemplo,

$ 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

Podrías ponerte elegante e incluir la eliminación de comillas en la extracción de valor-clave haciendo coincidir cero o más comillas en cada extremo de la secuencia de valores

$ 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

Usted podría conseguir realmente de lujo y emular el pastede seduniéndose primero pares de líneas en la ,\r$finalización y se emparejan los pares de valores clave se multiplican ( g) y no con avidez

$ 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, preferiría el enfoque KISS y usaría el primero).


FWIW, dado que su entrada parece estar JSON sobrecomillada, sugeriría instalar un analizador JSON adecuado como jq

sudo apt-get install jq

Entonces puedes hacer algo como

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

que elimina las comillas superfluas y luego las usa jqpara extraer los campos de interés; tenga en cuenta que jqparece manejar las terminaciones de línea de estilo DOS, por lo que no es necesario tomar medidas especiales para eliminarlas.

Cambie a jq '.[]'para volcar todos los pares de atributos-valores.

Crédito por la inspiración y la jqsintaxis básica tomada de Superar nuevas líneas con grep -o

conductor de acero
fuente
1
ugh sí, idk por qué lo olvidé \r. jqse rompió en la primera línea donde el campo del título tenía dos puntos (la primera línea). Todavía no estoy seguro de por qué sedme odia, pero Maté a algunas de las citas y \ren esta línea /,\r*/{N;/\n.*title.*:\s/{s/,\r*\n.*title.*:\s/,\ /}}y, finalmente, que funciona como este . Muchas gracias ^ _ ^
Zanna
1
Eso es MUCHO mejor (pero no quiero ninguna de las citas así sed -rn -e 's/\"\"//g' -e 's/^(.*): (.*)\r$/\2/p' QueryR* | paste -d '' - - y hecho como magia)
Zanna
5

Lo arreglé gracias a Steeldriver y otros ajustes. Sin refinar pero funciona.

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"

traducción:
s/"{//Eliminar "{
s/}"//Eliminar }"
s/^"//Eliminar "del inicio de la
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}}coincidencia de línea ,\ren una línea y [whatever]title[whatever]:en la siguiente línea, reemplazar todo eso con ,
s/""//gEliminar todas las comillas dobles dobles restantes
s/^\s\+//Eliminar el espacio en blanco del inicio de las líneas
/^\s*$/dEliminar las líneas vacías
s/^id:\ //Eliminar id:y dejar espacio después de eso
s/\\//gEliminar las barras invertidas (caracteres de escape para "agregado a algunos campos de título)
tee "$1"especifica un archivo de salida cuando se ejecuta el script, por ejemplo./queryclean newquery.csv

Zanna
fuente
4

Si bien la pregunta lo solicita sed, uno podría solucionar los problemas de 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('""'," "))

Este código es compatible con python2 y python3, por lo que cualquiera funcionará

Ejecución de muestra:

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
fuente
4

Tres enfoques más:

  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 expresiones regulares compatibles con perl y perl simple:

    $ 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
fuente
4

Esto no es exactamente responder a su pregunta o resolver su problema, pero para deshacerse de los caracteres no deseados puede usar tr :

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

y obtendrás:

Ingrese la descripción de la imagen aquí

kcdtv
fuente
gracias, necesito aprender a usar tr:)
Zanna
No es tan poderoso como sed o awk, pero es muy sencillo para ese tipo de cosas. Saludos :)
kcdtv
1

Este es otro guión escrito en Ruby. Retendrá las comas en el título, que pueden importarse fácilmente a cualquier programa de hoja de cálculo sin romper las columnas.

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

Después de ejecutar el programa, la salida producida se verá así

281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"
Anwar
fuente
Eso es muy agradable :)
Zanna
¿Qué hay de los títulos con :dentro de ellos?
Sнаđошƒаӽ
@ Sнаđошƒаӽ ¡Uy! Gracias por la anotación. Solucionado ahora!
Anwar