Awk considère la chaîne entre guillemets comme un seul jeton et ignore l'espace entre les deux

Fichier de Données - données.txt:

ABC "I am ABC" 35 DESC
DEF "I am not ABC" 42 DESC

cat data.txt | awk '{print $2}'

Entraînera le " I " au lieu de la chaîne entre guillemets

Comment faire awk pour qu'il ignore l'espace dans la citation et pense que c'est un seul jeton?

22
demandé sur Roy Chan 2011-07-08 07:12:58

7 réponses

Oui, cela peut être fait bien dans awk. Il est facile d'obtenir tous les champs sans aucun hacks sérieux.

(Cet exemple fonctionne à la fois dans le seul vrai Awk et dans gawk.)

{
  split($0, a, "\"")
  $2 = a[2]
  $3 = $(NF - 1)
  $4 = $NF
  print "and the fields are ", $1, "+", $2, "+", $3, "+", $4
}
7
répondu DigitalRoss 2011-07-08 04:03:05

Essayez ceci:

$ cat data.txt | awk -F\" '{print $2}'
I am ABC
I am not ABC
5
répondu Chris Gregg 2011-07-08 03:27:53

Une Autre alternative serait d'utiliser la FPAT variable, qui définit une expression régulière décrivant le contenu de chaque champ.

Enregistrez ce script AWK sous parse.awk:

#!/bin/awk -f

BEGIN {
  FPAT = "([^ ]+)|(\"[^\"]+\")"
}
{
  print $2
}

Le Rendre exécutable avec chmod +x ./parse.awk et analyser votre fichier de données comme ./parse.awk data.txt:

"I am ABC"
"I am not ABC"
4
répondu mabalenk 2017-04-04 17:25:41

J'ai rassemblé une fonction qui re-divise $ 0 dans un tableau appelé B. Les espaces entre guillemets doubles n'agissent pas comme des séparateurs de champs. Fonctionne avec n'importe quel nombre de champs, un mélange d'cotées et non cotées celles. Voici:

#!/usr/bin/gawk -f

# Resplit $0 into array B. Spaces between double quotes are not separators.
# Single quotes not handled. No escaping of double quotes.
function resplit(       a, l, i, j, b, k, BNF) # all are local variables
{
  l=split($0, a, "\"")
  BNF=0
  delete B
  for (i=1;i<=l;++i)
  {
    if (i % 2)
    {
      k=split(a[i], b)
      for (j=1;j<=k;++j)
        B[++BNF] = b[j]
    }
    else
    {
      B[++BNF] = "\""a[i]"\""
    }
  }
}

{
  resplit()

  for (i=1;i<=length(B);++i)
    print i ": " B[i]
}

J'espère que ça aide.

2
répondu arg0 2013-05-28 14:13:33

La première réponse à cette question ne fonctionne que pour les lignes avec un seul champ entre guillemets. Quand j'ai trouvé cette question, j'avais besoin de quelque chose qui pourrait fonctionner pour un nombre arbitraire de champs entre guillemets.

Finalement, je suis tombé sur une réponse de Wintermute dans un autre fil , et il a fourni une bonne solution généralisée à ce problème. Je viens de le modifier pour supprimer les guillemets. Notez que vous devez appeler awk avec -F\" lors de l'exécution du programme ci-dessous.

BEGIN { OFS = "" } {
    for (i = 1; i <= NF; i += 2) {
        gsub(/[ \t]+/, ",", $i)
    }
    print
}

Cela fonctionne par en observant que tous les autres éléments du tableau seront à l'intérieur des guillemets lorsque vous vous séparez par le caractère" -, et il remplace l'espace en divisant ceux qui ne sont pas entre guillemets par une virgule.

Vous pouvez ensuite facilement enchaîner une autre instance d'awk pour faire tout le traitement dont vous avez besoin (utilisez simplement le Commutateur Séparateur de champs, -F,).

Notez que cela pourrait casser si le premier champ est cité-Je ne l'ai pas testé. Si c'est le cas, cependant, il devrait être facile à corriger en ajoutant un if instruction pour démarrer à 2 plutôt que 1 si le premier caractère de la ligne est un ".

2
répondu khh 2017-05-23 12:18:12

OK, si vous voulez vraiment les trois champs, vous pouvez les obtenir, mais cela prend beaucoup de tuyauterie:

$ cat data.txt | awk -F\" '{print $1 "," $2 "," $3}' | awk -F' ,' '{print $1 "," $2}' | awk -F', ' '{print $1 "," $2}' | awk -F, '{print $1 "," $2 "," $3}'
ABC,I am ABC,35
DEF,I am not ABC,42

Par le dernier tuyau, vous avez les trois champs pour faire ce que vous voulez avec.

0
répondu Chris Gregg 2011-07-08 04:00:05

Voici quelque chose comme ce que j'ai finalement obtenu de travailler qui est plus générique pour mon projet. Notez qu'il n'utilise pas awk.

someText="ABC \"I am ABC\" 35 DESC '1 23' testing 456"
putItemsInLines() {
    local items=""
    local firstItem="true"
    while test $# -gt 0; do
        if [ "$firstItem" == "true" ]; then
            items="$1"
            firstItem="false"
        else
            items="$items
$1"
        fi
        shift
    done
    echo "$items"
}

count=0
while read -r valueLine; do
    echo "$count: $valueLine"
    count=$(( $count + 1 ))
done <<< "$(eval putItemsInLines $someText)"

Quelles sorties:

0: ABC
1: I am ABC
2: 35
3: DESC
4: 1 23
5: testing
6: 456
0
répondu bourne2program 2016-01-15 21:58:12