web-dev-qa-db-ja.com

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

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

Prelude最初に、次のようなリスト、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」という文字列が含まれています。

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

debug screenshot

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

何を間違えたのですか?

6
rGA145

私があなたを正しく理解していれば、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

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

_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
_
4
Isaac

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

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

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

2
jordanm

別のアプローチとして、この作業は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
_

リストにQでコメントされたCRLF行末(別名DOSまたはWindows形式)があり、それを(簡単に)できないか、最初に削除したくない場合、awkはそれも処理できます。 2番目の_{_の直後にsub(/\r$/,"",$0);を挿入します(必要に応じて_$2_を挿入します)。

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

0