Les séries de commandes sed fonctionnent sur la ligne de commande, mais pas dans un script

9

Je travaille avec la .csvsortie de cette requête de données SE qui ressemble à ceci (uniquement avec 5022 entrées):

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

(Et il a des ^Mfins de ligne entre [nombre] et "" titre ""). J'en ai besoin pour ressembler à ceci:

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

J'ai corrigé cela dans un certain éditeur de texte qui restera assez facilement sans nom, mais je voulais créer un script pour ne plus avoir à le refaire chaque fois que la requête est actualisée et que d'autres puissent l'utiliser. J'ai utilisé sed...

Cette série de commandes fonctionne parfaitement (même si elle peut bien être inefficace; ce n'est qu'une solution d'essai et d'erreur):

# 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

Alors, pourquoi ça? Seuls les ^Met {}sont supprimés, et tout le reste est toujours 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

Je suis sûr que mon erreur est vraiment évidente ...

Zanna
la source

Réponses:

11

L' utilisation cat -vde transformer les caractères CR dans littérales des ^Mséquences me semble fondamentalement laid - si vous devez supprimer les fins de ligne DOS, utilisez dos2unix, trou sed 's/\r$//'

Si vous insistez sur l' utilisation sed, alors je vous suggère d' imprimer les bits que vous ne voulez, plutôt que d' essayer de supprimer tous les bits aléatoires que vous n'avez pas - par exemple

$ 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

Vous pouvez obtenir de la fantaisie et rouler la suppression des guillemets dans l'extraction de valeurs-clés en faisant correspondre zéro ou plusieurs guillemets à chaque extrémité de la séquence de valeurs

$ 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

Vous pourriez obtenir vraiment de fantaisie et imiter le pastedans sedd'abord se joindre à des paires de lignes sur la ,\r$fin et ensuite correspondre les paires valeur de clé se multiplient ( g) et non goulûment

$ 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

(Personnellement, je préférerais l'approche KISS et j'utiliserais la première).


FWIW, étant donné que votre entrée semble être sur-cotée JSON, je suggère d'installer un analyseur JSON approprié tel que jq

sudo apt-get install jq

Vous pouvez alors faire quelque chose comme

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

qui supprime les guillemets superflus et les utilise ensuite jqpour extraire les champs d'intérêt - notez que cela jqsemble gérer les fins de ligne de style DOS, donc il n'est pas nécessaire de prendre des mesures spéciales pour les supprimer.

Passez à jq '.[]'pour vider toutes les paires attribut-valeur.

Crédit pour l'inspiration et la jqsyntaxe de base tirés de Surmonter les nouvelles lignes avec grep -o

tournevis
la source
1
ouais, je sais pourquoi j'ai oublié \r. jqcassé sur la première ligne où le champ de titre avait deux points (la première ligne). Je ne sais toujours pas pourquoi sedje me déteste, mais j'ai tué certaines des citations et \rdans cette ligne /,\r*/{N;/\n.*title.*:\s/{s/,\r*\n.*title.*:\s/,\ /}}et finalement ça fonctionne comme ça . Merci beaucoup ^ _ ^
Zanna
1
C'est BEAUCOUP mieux (mais je ne veux pas que les citations soient sed -rn -e 's/\"\"//g' -e 's/^(.*): (.*)\r$/\2/p' QueryR* | paste -d '' - - faites comme par magie)
Zanna
5

Je l'ai réparé grâce au tournevis en acier et au bricolage. Non raffiné mais fonctionne.

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"

Traduction:
s/"{//Supprimer "{
s/}"//Supprimer }"
s/^"//Supprimer "du début de la ligne
/,\r/{N;/\n.*title.*:\s/{s/,\r\n.*title.*:\s/,\ /}}correspondance ,\rsur une ligne et [whatever]title[whatever]:sur la ligne suivante, remplacer tout cela avec ,
s/""//gsupprimer tous les doubles guillemets doubles restants des
s/^\s\+//espaces Supprimer du début des lignes
/^\s*$/dSupprimer les lignes vides
s/^id:\ //Retirer id:et de l' espace après
s/\\//genlever les antislashs (caractères d'échappement pour "ajouté à certains champs de titre)
tee "$1"spécifiez un fichier externe lors de l'exécution du script, par exemple./queryclean newquery.csv

Zanna
la source
4

Alors que la question le demande sed, on pourrait contourner les problèmes de sed avec 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('""'," "))

Ce code est compatible avec python2 et python3, donc l'un ou l'autre fonctionnera

Exemple d'exécution:

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
la source
4

Trois autres approches:

  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. Grep GNU avec des expressions rationnelles compatibles perl et simple perl:

    $ 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
la source
4

Ce n'est pas exactement répondre à votre question ou résoudre votre problème, mais pour vous débarrasser des caractères indésirables, vous pouvez utiliser tr :

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

et vous obtiendrez:

Entrez la description de l'image ici

kcdtv
la source
merci, j'ai besoin d'apprendre à utiliser tr:)
Zanna
Ce n'est pas aussi puissant que sed ou awk mais c'est très simple pour ce genre de choses.
Santé
1

Ceci est un autre script écrit en Ruby. Il conservera les virgules dans le titre, qui peuvent être facilement importées dans n'importe quel programme de feuille de calcul sans casser les colonnes.

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

Après l'exécution du programme, la sortie produite ressemblera à ceci

281952,"Flash 11.2 No Longer Supported by Google Play"
281993,"Netbeans won't open in Ubuntu"
Anwar
la source
C'est très bien :)
Zanna
Que diriez-vous des titres avec à l' :intérieur d'eux?
Sнаđошƒаӽ
@ Sнаđошƒаӽ oups! Merci pour le pointeur. Fixé maintenant!
Anwar