将 awk 的输出存储到数组并将其打印到带有逗号分隔符的文件

将 awk 的输出存储到数组并将其打印到带有逗号分隔符的文件

我正在尝试使用 bash 遍历多个目录 (sims) 并搜索给定的字符串,将数组的每个索引设置为其相关输出,然后使用逗号分隔符打印该输出,每个值都使用逗号分隔符,并添加分号分隔符来分隔每个文件。实际上,这应该会给我一个可以在 excel 中拆分两次的 CSV。每次找到“总能量”时,所有重要信息都在字段 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),并且在移动到下一个目录之前似乎会重复数千次。

我已经搜索了一段时间,但我不明白为什么会发生这种情况。我还尝试在每次迭代结束时取消设置能量,但无济于事。所以我的具体问题是:为什么在循环中第一次将 awk 输出分割到数组中可以工作,但随后的任何一次都不起作用?有没有更好/更有效的方法可以使用 bash 来解决这个问题,值得一试?

答案1

第一次通过循环时,您进行了设置energy=( $(awk ...) ),输出由awk几行组成,每行包含一个数字并以换行符结尾,就像 Unix 中的行一样。命令替换$( ... )会删除尾随换行符,当它不在双引号中时,然后在任何空格制表符换行符处将结果分解为“单词”(您有换行符),最后如果任何单词是“模式” (包含?*[..])匹配任何文件名,它被这些文件名替换为单独的“单词”(您没有这样的“glob”模式)。然后,数组赋值energy=( ... )将这些单词存储为数组的元素。

第二次通过 IFS 设置为逗号。现在,当$( ... )尝试拆分为单词时,它(仅)使用逗号,并且 awk 的输出中没有逗号,因此整个输出(包括换行符)保留为字,并被分配给数组元素。

您需要为每次迭代恢复 IFS。此外,您还需要将 IFS 设置为其标准值,或者至少设置为包含换行符的值入口到这个脚本。 OTOH 在退出脚本之前恢复 IFS 几乎没有用;脚本通常在单独的 shell 进程中运行,并且脚本退出时,脚本所做的任何变量设置或其他进程内更改都将被丢弃。

或者,您可以不更改 IFS 并显式恢复做在一个子外壳以便在子 shell 完成时放弃更改。子 shell 的 shell 语法是括号,这次是单独的:

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

一般来说也更printf安全,echo因为根据您使用的 shell 和/或系统echo可能会破坏某些字符串值。但是,您此处的值(仅限于十进制数字)对于echo.

对于 bash,另一种可能性是将数据视为单个字符串,而不是数组:

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

使用其他 awk,您可以通过(首先!)检查FNR==1&&NR>1除最后​​一个文件之外的每个文件的结尾和(任何地方)END最后一个文件的结尾,使用稍微丑陋的代码来完成相同的事情。

相关内容