web-dev-qa-db-ja.com

awkは二重引用符で囲まれた文字列を1つのトークンと見なし、その間のスペースを無視します

データファイル-data.txt:

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

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

引用符で囲まれた文字列の代わりに「I」になります

引用符内のスペースを無視して1つのトークンであると考えるようにawkを作成する方法は?

25
Roy Chan

はい、これはawkでうまく実行できます。深刻なハックなしにすべてのフィールドを取得するのは簡単です。

(この例は The One True Awk とgawkの両方で機能します。)

{
  split($0, a, "\"")
  $2 = a[2]
  $3 = $(NF - 1)
  $4 = $NF
  print "and the fields are ", $1, "+", $2, "+", $3, "+", $4
}
8
DigitalRoss

別の代替案は、各フィールドの内容を説明する正規表現を定義する FPAT 変数を使用することです。

このAWKスクリプトをparse.awkとして保存します。

#!/bin/awk -f

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

chmod +x ./parse.awkで実行可能にし、データファイルを./parse.awk data.txtとして解析します。

"I am ABC"
"I am not ABC"
12
mabalenk

これを試して:

$ cat data.txt | awk -F\" '{print $2}'
I am ABC
I am not ABC
5
Chris Gregg

この質問の一番上の答えは、単一引用符で囲まれたフィールドがある行に対してのみ機能します。この質問を見つけたとき、任意の数の引用フィールドで機能するものが必要でした。

結局私は偶然出会った 別のスレッドでWintermuteによる回答 、そして彼はこの問題に対する優れた一般化された解決策を提供した。引用符を削除するように変更しました。以下のプログラムを実行するときは、-F\"でawkを呼び出す必要があることに注意してください。

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

これは、「-」文字で区切ると、配列内の他のすべての要素が引用符の内側にあることを確認することで機能し、引用符で囲まれていないものを区切る空白をカンマで置き換えます。

次に、awkの別のインスタンスを簡単にチェーンして、必要な処理をすべて実行できます(フィールド区切りスイッチ-F,を再度使用するだけです)。

最初のフィールドが引用符で囲まれていると、これが壊れる可能性があることに注意してください-私はテストしていません。その場合、行の最初の文字が "の場合、ifステートメントを追加して1ではなく2から開始することで簡単に修正できます。

3
khh

$ 0をBと呼ばれる配列に再分割する関数をまとめました。二重引用符の間のスペースは、フィールドセパレーターとして機能していません。引用符付きと引用符なしのフィールドが混在する任意の数のフィールドで機能します。ここに行く:

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

それが役に立てば幸い。

2
arg0

3つのフィールドすべてが本当に必要な場合は、それらを取得できますが、多くのパイプ処理が必要です。

$ 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

最後のパイプまでに、3つのフィールドすべてが好きなように実行できます。

0
Chris Gregg

これは私が最終的に作業したもののようなもので、私のプロジェクトにとってより一般的です。 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)"

どの出力:

0: ABC
1: I am ABC
2: 35
3: DESC
4: 1 23
5: testing
6: 456
0
bourne2program