Série de comandos sed funcionam na linha de comando, mas não em um script

9

Estou trabalhando com a .csvsaída desta consulta de dados SE que se parece com isso (apenas com entradas 5022):

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

(E possui ^Mfinais de linha entre [número] e "" título ""). Eu preciso que fique assim:

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

Corrigi isso em um determinado editor de texto que permanecerá sem nome com muita facilidade, mas eu queria criar um script para não precisar fazer isso novamente toda vez que a consulta for atualizada e para que outros possam usá-lo. Eu usei sed...

Essa série de comandos funciona perfeitamente (embora possa ser ineficiente; é apenas uma solução de tentativa e erro):

# 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

Então, por que não faz isso? Apenas o ^Me {}são removidos, e todo o resto ainda está 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

Tenho certeza que meu erro é realmente óbvio ...

Zanna
fonte

Respostas:

11

Usando cat -vpara transformar caracteres CR em literais ^Mseqüências parece fundamentalmente feio para mim - se você precisa remover finais de linha DOS, uso dos2unix, trou sed 's/\r$//'

Se você insistir em usar sed, então sugiro que você imprimir os bits que você não quer, ao invés de tentar apagar todos os bits aleatórios que não fazer - por exemplo,

$ 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

Você pode imaginar e rolar a remoção de cotação para a extração de valor-chave, correspondendo zero ou mais cotações em cada extremidade da sequência 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

Você pode se sentir realmente sofisticado e imitar o pastein sed, primeiro juntando pares de linhas no ,\r$final e depois combinando os pares de valores-chave multiply ( g) e não gananciosamente

$ 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

(Pessoalmente, eu preferiria a abordagem KISS e usaria a primeira).


FWIW, como sua entrada parece ser JSON com aspas excessivas, sugiro instalar um analisador JSON adequado, como jq

sudo apt-get install jq

Você pode fazer 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 remove as aspas supérfluas e depois usa jqpara extrair os campos de interesse - observe que jqparece manipular as terminações de linha no estilo DOS, portanto, não é necessário tomar medidas especiais para removê-las.

Mude para jq '.[]'para despejar todos os pares atributo-valor.

Crédito pela inspiração e jqsintaxe básica obtida em Superando novas linhas com grep -o

chave de aço
fonte
11
ugh sim, idk porque eu esqueci \r. jqquebrou na primeira linha onde o campo de título tinha dois pontos (a primeira linha). Eu ainda não tenho certeza por que sedme odeia, mas eu matei algumas das citações e \rnesta linha /,\r*/{N;/\n.*title.*:\s/{s/,\r*\n.*title.*:\s/,\ /}}e, finalmente, ele funciona como este . Muito obrigado ^ _ ^
Zanna
11
Isso é muito melhor (mas eu não quero nenhuma das citações assim sed -rn -e 's/\"\"//g' -e 's/^(.*): (.*)\r$/\2/p' QueryR* | paste -d '' - - e feito como mágica)
Zanna
5

Eu o consertei graças à chave de aço e outros ajustes. Não refinado, mas 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"

Tradução:
s/"{//Remove "{
s/}"//Remove }"
s/^"//Remove "do início da linha de
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}}partida ,\rem uma linha e [whatever]title[whatever]:na próxima linha, substituir tudo isso com ,
s/""//gRemova todos os restantes dupla aspas
s/^\s\+//Remover espaços em branco do início de linhas
/^\s*$/dRemover linhas vazias
s/^id:\ //Remover id:e espaço depois que
s/\\//gRemover barras invertidas (caracteres de escape para "adicionado a alguns campos de título)
tee "$1"especifique um arquivo externo ao executar o script, por exemplo./queryclean newquery.csv

Zanna
fonte
4

Enquanto a pergunta é solicitada sed, é possível solucionar os problemas do sed com o 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('""'," "))

Esse código é compatível com python2 e python3, portanto, ambos funcionarão

Exemplo de execução:

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

Mais três abordagens:

  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 com regexes compatíveis com perl e perl simples:

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

Isso não está exatamente respondendo à sua pergunta ou resolvendo o problema, mas para se livrar dos caracteres indesejados, você pode usar tr :

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

e você terá:

Digite a descrição da imagem aqui

kcdtv
fonte
obrigado, eu preciso aprender a usar tr:)
Zanna
Não é tão poderoso quanto sed ou awk, mas é muito direto para esse tipo de coisa. Vivas :)
kcdtv
1

Este é outro script escrito em Ruby. Ele reterá as vírgulas no título, que podem ser facilmente importadas para qualquer programa de planilha sem quebrar as colunas.

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

Após a execução do programa, a saída produzida será semelhante à

281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"
Anwar
fonte
Isso é muito bom :)
Zanna
Que tal títulos com :dentro deles?
Sнаđошƒаӽ
@ Sнаđошƒаӽ oops! Obrigado pelo ponteiro. Corrigido agora!
Anwar