スクリプトに問題があります。
序章 まず、次のような 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 ではありません。