awk が区切り文字として「スペース」を無視しないのはなぜですか?

awk が区切り文字として「スペース」を無視しないのはなぜですか?

スクリプトに問題があります。

序章 まず、次のような 100 行のリストファイルがあります。

100;TEST ONE
101;TEST TWO
...
200;TEST HUNDRED

各行には 2 つの引数があります。たとえば、最初の行の引数は「645」、「TEST ONE」です。したがって、セミコロンが区切り文字です。

両方の引数を 2 つの変数に入れる必要があります。$id と $name とします。各行の $id と $name の値は異なります。たとえば、2 行目は $id = "646"、$name = "TEST TWO" です。

その後、サンプル ファイルを取得して、定義済みのキーワードを $id と $name の値に変更する必要があります。サンプル ファイルは次のようになります。

xxx is yyy

その結果、異なるコンテンツを持つ 100 個のファイルが必要になります。各ファイルには、すべての行の $id と $name データが含まれている必要があります。また、$name 値で名前を付ける必要があります。

私のスクリプトは次のとおりです:

#!/bin/bash -x
rm -f output/*

for i in $(cat list)
    do

        id="$(printf "$i" | awk -F ';' '{print $1}')"
        name="$(printf "$i" | awk -F ';' '{print $2}')"

        cp sample.xml output/input.tmp

        sed -i -e "s/xxx/$id/g" output/input.tmp
        sed -i -e "s/yyy/$name/g" output/input.tmp

        mv output/input.tmp output/$name.xml


    done

そこで、リスト ファイルを 1 行ずつ読み取ろうとします。行ごとに 2 つの変数を取得し、それらを使用してサンプル ファイルのキーワード (xxx と yyy) を置き換え、結果を保存します。

しかし、何かがおかしくなった

結果的に、出力ファイルは 1 つだけになりました。デバッグもうまくいきません。

これは、リスト ファイルに 2 行だけあるデバッグ ウィンドウです。出力ファイルは 1 つしかありません。ファイル名は単に「TEST」で、文字列「101 is TEST」が含まれています。

2 つのファイルが必要です: 「TEST ONE」、「TEST TWO」。また、「100 is TEST ONE」および「101 is TEST TWO」が含まれている必要があります。

デバッグスクリーンショット

ご覧のとおり、2 番目の変数にはスペースが含まれています (たとえば、「TEST ONE」)。この問題はスペース特殊記号に関連していると思いますが、その理由はわかりません。-F awk パラメータを「;」に設定しているため、awk はセミコロンのみを区切り文字として解釈する必要があります。

何を間違えたのでしょうか?

答え1

私の理解が正しければ、whileループと変数の展開を使用することができます

while IFS= read -r line; do 
  id="${line%;*}"
  name="${line#*;}"
  cp sample.xml output/input.tmp
  sed -i -e "s/xxx/$id/g" output/input.tmp
  sed -i -e "s/yyy/$name/g" output/input.tmp
  mv output/input.tmp output/"$name".xml
done < file

@steeldriver の提案ここに(よりエレガントな)オプションがあります:

while IFS=';' read -r id name; do 
  cp sample.xml output/input.tmp
  sed -i -e "s/xxx/$id/g" output/input.tmp
  sed -i -e "s/yyy/$name/g" output/input.tmp
  mv output/input.tmp output/"$name".xml
done < file

答え2

引用!! この行の引用がありません:

mv output/input.tmp output/$name.xml

そのはず:

mv output/input.tmp output/"$name".xml

スペースを含むファイル名に関する問題を回避します。

また、 の展開は$(cat list)シェルによって分割 (およびグロブ化) されており、これもスペースで分割されます。

次のスクリプトに変更できるかもしれません:

#!/bin/bash -x
rm -f output/*

inputfile=output/input.tmp

while read -r line
do
    id=${line%%;*}
    name=${line##*;}

    cp sample.xml "$inputfile"
    sed -i -e "s/xxx/$id/g" "$inputfile"
    sed -i -e "s/yyy/$name/g" "$inputfile"
    mv "$inputfile"  output/"$name".xml; echo

done <list

答え3

awk が期待どおりの結果を生成しない理由は、ファイルに対する反復処理の方法によるものです。 を使用して反復処理する場合for i in $(cat file)、行ではなく単語 (IFS で分割) に対して反復処理を行います。 ファイルを行ごとに読み取るには、 を使用しますwhile read

while read -r line; do
    ...
done < file

詳細については、次の bash FAQ を参照してください。ファイル (データ ストリーム、変数) を行ごとに (および/またはフィールドごとに) 読み取るにはどうすればよいですか?

答え4

代替アプローチとして、この作業はawkで行うことができます各行ごとに 4 つのプロセスではなく 1 つのプロセスで実行されます。これは、リストに多くの行があるが sample.xml が小さい場合に最も効果的です。

awk -F';' 'FNR==NR{x=x $0 RS; next} 
{t=x; gsub(/xxx/,$1,t); gsub(/yyy/,$2,t); f="output/"$2".xml"; printf "%s",t >f; close(f)}
' sample.xml list
# shown with unnecessary linebreaks for clarity, but you can put it all on one line

質問にコメントされているように、リストに CRLF 行末 (つまり DOS または Windows 形式) があり、最初にそれらを削除できない (簡単には) または削除したくない場合は、awk でそれを処理することもできます (2 番目の{挿入の直後sub(/\r$/,"",$0);、または$2希望する場合)。

perl でも同じことができます (perl は awk でできることのほとんどすべてを実行できます) が、少し冗長になります。また、perl は一般的に利用可能ですが、awk のような POSIX ではありません。

関連情報