web-dev-qa-db-fra.com

Une série de commandes sed fonctionne en ligne de commande, mais pas dans un script

Je travaille avec la sortie .csv 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 la ligne ^M se termine entre [nombre] et "" titre ""). J'ai besoin que ça ressemble à 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 doit rester sans nom assez facilement, mais je voulais créer un script afin de ne pas 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 (bien qu’elle puisse être inefficace; ce n’est qu’une solution empirique):

# 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 ^M et {} 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 ...

9
Zanna

Utiliser cat -v pour transformer les caractères CR en séquences littérales ^M me semble fondamentalement moche - si vous devez supprimer les fins de ligne DOS, utilisez dos2unix, tr ou sed 's/\r$// '

Si vous insistez pour utiliser sed, alors je vous suggère d’imprimer les bits que vous voulez plutôt que d’essayer de supprimer tous les bits aléatoires que vous ne voulez 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 une certaine fantaisie et intégrer la suppression de guillemets à l'extraction clé-valeur en faisant correspondre les guillemets à zéro ou plus à chaque fin 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 pouvez obtenir vraiment des fantaisies et émuler la paste dans sed en joignant d’abord des paires de lignes sur le ,\r$ se terminant puis en faisant correspondre les paires clé-valeur se multiplient (g) et de manière non-gourmande

$ 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 utiliserais le premier).


FWIW, puisque votre entrée semble trop compter sur JSON, je suggérerais d'installer un analyseur JSON approprié tel que jq

Sudo apt-get install jq

Vous pouvez ensuite 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 utilise ensuite jq pour extraire les champs d’intérêt. Notez que jq semble gérer les fins de ligne de style DOS. Il n’est donc 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 syntaxe de base jq tirée de Surmonter des lignes nouvelles avec grep -o

11
steeldriver

Je l'ai réparé grâce à Steeldriver & bricoler davantage. 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/,\ /}} fait correspondre ,\r sur une ligne et [whatever]title[whatever]: sur la ligne suivante, remplace tout cela par ,
s/""//g Supprimer tous les guillemets doubles restants.
s/^\s\+// Supprimer les espaces du début des lignes
/^\s*$/d Supprimer les lignes vides
s/^id:\ // Supprimez id: et espacez-le
s/\\//g Supprimer les barres obliques inverses (caractères d'échappement pour "ajoutés à certains champs de titre)
tee "$1" spécifiez un fichier de sortie lors de l'exécution du script, par exemple ./queryclean newquery.csv

5
Zanna

Bien que la question 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 cela fonctionnera

Échantillon échantillon:

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
Sergiy Kolodyazhnyy

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. GNU grep avec les expressions rationnelles compatibles Perl et le langage 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
    
4
terdon

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

Enter image description here

4
kcdtv

Ceci est un autre script écrit en Ruby. Il conservera les virgules dans le titre, ce qui peut être facilement importé dans n’importe quel tableur 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"
1
Anwar