awkの出力を配列に保存し、コンマ区切り文字を含むファイルとして印刷します。

awkの出力を配列に保存し、コンマ区切り文字を含むファイルとして印刷します。

bashを使用して複数のディレクトリ(sims)を繰り返し、指定された文字列を検索し、配列内の各インデックスを関連する出力に設定し、各値にコンマ区切り文字を使用してその出力を印刷しようとしています。そして、セミコロン区切り文字を追加します。各ファイルを分離してください。効果的には、Excelで2回分割できるCSVを提供する必要があります。 「Total Energy」が見つかるたびに、すべての重要な情報は各ディレクトリの「Output」ファイルのフィールド3にあります。

現在テストされているディレクトリセットの場合、各出力ファイルには2500個のエネルギーが必要ですが、現在のコードではそれを見つけることができません(下記参照)。

これは私のコードです。

#/bin/bash/

saveIFS="$IFS"

#Step 1: Ask user for the range of sims they want
echo "What is the first sim?"
read simcount
echo "What is the last sim?"
read simend

#Step 2: Create the energy files with proper naming conventions and make sure they're empty
energies+="energies${simcount}-${simend}.csv"
fenergies+="final_energies${simcount}-${simend}.out"
touch $energies
touch $fenergies
< $energies
< $fenergies

#Step 3: Go through each directory, print all energies into proper files
while [ $simcount -le $simend ]; do
        echo $simcount
        cd $print'sim'$simcount                                 # Change to the directory of each specified sim
        energy=($(awk '/Total Energy/{ print $3 }' output))     # Print all energies from output into an array
        echo ${#energy[@]}
        fenergy=${energy[${#energy[@]}-1]}                      # Get the last energy in each file
        cd ../                                                  # Go up a directory
        IFS=","                                                 # Change the Internal Field Separator (IFS) to a comma
        echo "${energy[*]};" >> $energies                       # Expand the array of energies into an IFS-delimited list; print it into the new energies file
        echo "$fenergy" >> $fenergies                           # Put the final energy of each sim on a new line in the new final energies file
        ((simcount++))
done

IFS="$saveIFS"

exit 0

これにより、次の出力が提供されます。

$ e.sh
What is the first sim?
6
What is the last sim?
15
6
2500
7
1
8
1
9
1
10
1
11
1
12
1
13
1
14
1
15
1

これは、ループが最初に2500のエネルギーをすべて見つけたが、その後ループを通過するたびにawkの出力を配列に分割しないことを意味します。 $energys という新しいファイルに出力される代表的な例は次のとおりです。

-271.2872230353,-271.3198859908,-271.4166545741,-271.5362409096,-271.6700236287,-271.8068505329,-271.9076587286,...;
-273.2853761106
-273.2855419371
...
-273.2856368361
-273.2857720402
-273.2859963834;
-271.2872230353
-271.3198859908
-271.4166545741
...

明確にするために、ループの最初の反復が成功し、セミコロン区切り文字を使用して1行に配列を出力します。すべての後続の反復は配列に分割されず(または長さが1の配列を持つ)、次のディレクトリに移動する前に数千回繰り返されるようです。

しばらく検索してみましたが、なぜこれが起こるのかわかりません。また、各繰り返しが終わるたびにエネルギー設定を解除しようとしましたが、役に立ちませんでした。だから私の具体的な質問は次のとおりです。 awk出力を配列に分割することがループで最初は機能しますが、それ以降は機能しないのはなぜですか?試してみる価値のあるbashを使用してこの問題を解決するより良い効率的な方法はありますか?

ベストアンサー1

最初のループを通過するときにUnixの行と同様に、それぞれ数字を含み、改行文字で終わる複数の行で構成されるようにenergy=( $(awk ...) )出力を設定します。awkコマンド置換は$( ... )二重引用符で囲まれていない末尾の改行を削除し、空白のタブの改行から結果を「単語」(改行がある場所)に分割し、最後に単語が「パターン」(含まれている)の?*[..]場合はファイル名と一致します。 。個々の「単語」を含むファイル名に置き換えられます(「glob」パターンはありません)。次に、配列割り当ては、energy=( ... )これらの単語を配列の要素として保存します。

IFS を介した 2 番目の転送はコンマに設定されます。$( ... )単語に分割しようとするとコンマだけが使用され、awkの出力にはコンマがないため、出力全体(改行を含む)は次のように保持されます。一つ単語として配列に割り当てる一つ要素

各繰り返しごとにIFSを復元する必要があります。また、IFSを標準値に設定するか、少なくとも改行文字を含む値に設定する必要があります。入り口このスクリプトで。 OTOHスクリプトを終了する前にIFSを復元することはほとんど役に立ちません。スクリプトは通常、別のシェルプロセスで実行され、スクリプトが終了したときにスクリプトによって行われた変数の設定や他のプロセス内の変更は削除されます。

または、IFSを変更せずにそのままにして明示的に復元できます。〜で行うサブシェルサブシェルが完了したら、変更を削除します。サブシェルのシェル構文は次のとおりです。返品今回だけ大括弧:

( IFS=","; echo "${energy[*]};" >> $energies )
# you don't actually need to quote , here but 
# it's a good habit for string literals in general

また、使用しているシェルおよび/またはシステムによっては、一部の文字列値が破損する可能性があるため、通常はprintf安全です。ただし、ここにある値(10進数のみ)は有効ではありません。echoechoecho

Bashの場合のもう1つの可能性は、データを配列ではなく単一の文字列として扱うことです。

energy=$( awk '/Total energy/{print $3}' output )
# command substitution strips the last newline
# scalar assignment does NOT do wordsplit and glob 
echo "${energy//$'\n'/,};" >>energies_blah 
# replaces all other newlines with commas, and adds semicolon 
echo "${energy##*$'\n'}" >>final_energies_blah 
# removes everything up to and including the last newline, 
# leaving only the last number

あるいは、実際にawkを使用してすべての操作を実行できます。特に 'endfile' を含む非古代の GNU awk を使うともっとそうです。

# read simcount,simend and set energies,fenergies
infiles=$( printf 'sim%d/output ' $( seq $simcount $simend ) )
awk -vf1=$energies -vf2=$fenergies '/Total Energy/ {e=e","$3; f=$3} ENDFILE {print substr(e,2)";">>f1; print f>>f2; e=f=""}' $infiles

FNR==1&&NR>1別のawkを使用すると(最初に!)、最後のファイルを除くすべてのファイルの終わりと最後のファイルの終わり(どこでも)を確認して、少しEND見苦しいコードで同じことを行うことができます。

おすすめ記事