bash를 사용하여 여러 디렉터리(sims)를 반복하고 주어진 문자열을 검색하고 배열의 각 인덱스를 관련 출력으로 설정한 다음 각 값에 대해 쉼표 구분 기호를 사용하여 해당 출력을 인쇄하려고 합니다. 그리고 세미콜론 구분 기호를 추가합니다. 각 파일을 분리하세요. 효과적으로, 이는 Excel에서 두 번 분할할 수 있는 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인 배열을 가짐) 다음 디렉터리로 이동하기 전에 수천 번 반복되는 것처럼 보입니다.
한동안 검색해 보았지만 왜 이런 일이 발생하는지 이해하지 못합니다. 또한 각 반복이 끝날 때마다 에너지 설정을 해제하려고 시도했지만 소용이 없었습니다. 그래서 내 구체적인 질문은 다음과 같습니다. awk 출력을 배열로 분할하는 것이 루프에서 처음에는 작동하지만 이후에는 작동하지 않는 이유는 무엇입니까? 시도해 볼 가치가 있는 bash를 사용하여 이 문제를 해결하는 더 좋고 효율적인 방법이 있습니까?
답변1
처음 루프를 통과할 때 Unix의 라인과 마찬가지로 각각 숫자를 포함하고 개행 문자로 끝나는 여러 라인으로 구성되도록 energy=( $(awk ...) )
출력을 설정합니다. awk
명령 대체는 $( ... )
큰따옴표로 묶이지 않은 후행 줄 바꿈을 제거한 다음 공백 탭 줄 바꿈에서 결과를 "단어"(줄 바꿈이 있는 곳)로 나누고 마지막으로 단어가 "패턴"(포함 ?*[..]
)인 경우 파일 이름과 일치합니다. 개별 "단어"가 포함된 파일 이름으로 대체됩니다("glob" 패턴이 없음). 그런 다음 배열 할당은 energy=( ... )
이러한 단어를 배열의 요소로 저장합니다.
IFS를 통한 두 번째 전달은 쉼표로 설정됩니다. 이제 $( ... )
단어로 분할하려고 할 때 쉼표만 사용하고 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
안전합니다 . 그러나 여기에 있는 값(십진수로만 제한)은 에 유효하지 않습니다 .echo
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
FNR==1&&NR>1
다른 awk를 사용하면 (먼저!) 마지막 파일을 제외한 모든 파일의 끝과 마지막 파일의 끝(어디에서든)을 확인하여 END
약간 더 보기 흉한 코드로 동일한 작업을 수행 할 수 있습니다 .