for 루프에서 백틱을 사용하지 않는 이유

for 루프에서 백틱을 사용하지 않는 이유

얼마 전 스크립팅에 관한 질문에 대한 답변을 게시한 적이 있습니다. 누군가 다음 명령을 사용하면 안 된다고 지적했습니다.

for x in $(cat file); do something; done 

하지만 대신:

while read f; do something; done < file

고양이의 쓸모없는 사용기사에서는 전체 문제를 설명하고 싶었지만 유일한 설명은 다음과 같습니다.

백틱의 결과가 쉘이 허용할 수 있는 명령줄의 길이보다 작거나 같다는 것을 알지 못하는 한 백틱은 매우 위험합니다. (실제로 이것은 커널 제한입니다.limits.h의 상수 ARG_MAX는 시스템이 얼마나 걸릴 수 있는지 알려줍니다. POSIX에서는 ARG_MAX가 최소 4,096바이트가 되도록 요구합니다.

이것을 올바르게 이해했다면 명령에서 매우 큰 파일의 출력을 사용하면 bash(?)가 충돌해야 합니다(limits.h 파일에 정의된 ARG_MAX를 초과해야 함). 그래서 다음 명령으로 ARG_MAX를 확인했습니다.

> grep ARG_MAX /usr/src/kernels/$(uname -r)/include/uapi/linux/limits.h
#define ARG_MAX       131072    /* # bytes of args + environ for exec() */

그런 다음 공백 없이 텍스트가 포함된 파일을 만들었습니다.

> ls -l
-rw-r--r--. 1 root root 100000000 Aug 21 15:37 in_file

그런 다음 다음을 실행합니다.

for i in $(cat in_file); do echo $i; done

아, 끔찍한 일은 없었어요.

그렇다면 "루프가 있는 고양이를 사용하지 마십시오" 전체가 위험한지/어떻게 확인하려면 어떻게 해야 합니까?

답변1

file포함하려는 항목 에 따라 다릅니다 . 예를 들어, IFS로 구분된 쉘 전역 목록을 포함하려는 경우(기본값은 이라고 가정 $IFS):

/var/log/*.log /var/adm/*~
/some/dir/*.txt

자, 이것이 for i in $(cat file)갈 길입니다. 인용 해제가 하는 일은 다음과 같습니다. $(cat file)후행 개행 문자를 제거한 상태로 출력에 분할+글로브 연산자를 적용합니다. cat file따라서 이러한 glob의 확장으로 인해 발생하는 각 파일 이름을 반복합니다(glob이 어떤 파일과도 일치하지 않는 경우, 이 경우 glob은 그대로 유지되지만 확장되지는 않습니다).

분리된 각 행을 반복하려면 file다음과 같이 하세요.

while IFS= read -r line <&3; do
{
  something with "$line"
} 3<&-
done 3< file

루프를 사용하면 for다음을 사용하여 비어 있지 않은 각 줄을 반복할 수 있습니다.

IFS='
' # 개행 문자로만 분할합니다(실제로 일련의 개행 문자와
  # 줄바꿈은 다음과 같기 때문에 선행 및 후행 문자를 무시합니다.
  #IFS 공백 문자)
set -o noglob # 비활성화전반적인 상황분할+글로브 연산자의 일부:
$(cat 파일)의 행에 대해 다음을 수행하십시오.
   "$line"이 있는 것
완벽한

하지만:

while read line; do
  something with "$line"
done < file

그것은 말이 되지 않습니다. 그건file매우 복잡한 방식으로 콘텐츠 읽기여기서 $IFS및 백슬래시 문자는 특별하게 처리됩니다.

어쨌든 인용한 텍스트가 참조하는 ARG_MAX 제한은 execve()시스템 호출(인수 및 환경 변수의 누적 크기와 관련)에 있으므로 적용될 수 있는 파일 시스템을 사용하여 명령 실행이 수행되는 경우에만 적용됩니다. 명령 대체 분할+글로브 연산자의 매우 긴 확장입니다(텍스트가 여러 계정에서 오해의 소지가 있고 잘못되었습니다).

예를 들어 다음과 함께 작동합니다.

cat -- $(cat file) # with shell implementations where cat is not builtin

하지만 다음에서는 그렇지 않습니다.

for i in $(cat file)

execve()시스템 호출은 포함되지 않습니다 .

비교하다:

bash-4.4$ echo '/*/*/*/*' > file
bash-4.4$ true $(cat file)
bash-4.4$ n=0; for f in $(cat file); do ((n++)); done; echo "$n"
523696
bash-4.4$ /bin/true $(cat file)
bash: /bin/true: Argument list too long

bash사용된 true내장 명령이나 루프는 작동 for하지만 실행은 작동하지 않습니다 /bin/true. 크기는 9바이트에 불과하지만 쉘이 글로브를 확장하므로 몇 메가바이트 file만큼 확장됩니다 $(cat file)./*/*/*/*

추가 자료:

답변2

@chepna댓글의 차이점을 설명했습니다.

for i in $(cat in_file)파일의 행을 반복하지 않고 파일 내용의 토큰화 및 경로 이름 확장으로 인해 발생하는 단어를 반복합니다.

성능 및 리소스 사용량에 대한 영향을 확인하기 위해 1M 행(~19M) 입력을 사용하여 두 경우 모두에 대한 작은 벤치마크를 수행하고 다음을 사용하여 시간과 메모리 사용량을 측정했습니다 /usr/bin/time -v.

test1.sh:

#!/bin/bash
while read x
do
    echo $x > /dev/null
done < input

결과:

Command being timed: "./test1.sh"
User time (seconds): 12.41
System time (seconds): 2.03
Percent of CPU this job got: 110%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:13.07
Maximum resident set size (kbytes): 3088

test2.sh:

#!/bin/bash
for x in $(cat input)
do
    echo $x > /dev/null
done

결과:

Command being timed: "./test2.sh"
User time (seconds): 17.19
System time (seconds): 3.13
Percent of CPU this job got: 109%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:18.51
Maximum resident set size (kbytes): 336356

두 테스트의 전체 결과를 다음에 업로드했습니다.페이스트빈. Bash를 사용하면 for i in $(cat ...)더 많은 메모리를 차지하고 실행 속도가 느려집니다. 그러나 동일한 테스트를 다른 셸에서 실행하는지 여부에 따라 결과가 달라질 수 있습니다.

답변3

while루프에 문제가 있을 수 있습니다. 가장 분명한 것은 기본적으로 stdin을 사용한다는 것입니다(so ssh -n). 따라서 다른 것에 stdin이 필요한 경우 while루프는 실패합니다.

$ find . -name "*.pm" | while read f; do aspell check $f; done
$ 

아무 것도 하지 않지만 aspellPerl 모듈 이름 목록이 차지하는 터미널이 필요합니다. 루프가 for더 적합합니다(파일 이름이 POSIX 토큰화 규칙에 의해 분할되지 않는다고 가정).

$ for f in $(find . -name \*.pm); do aspell check $f; done
...

while기본적으로 표준 입력을 사용하지 않기 때문입니다.

또한 while자동 데이터 손실이 발생하기 쉽습니다( for동일한 입력에 대해 다르게 동작함).

$ echo -n mmm silent data loss | while read line; do echo $line; done
$ for i in $(echo -n mmm silent data loss); do echo $i; done
mmm
silent
data
loss
$ 

while따라서 상황에 따라 위험하고 사용해서는 안 되는 주장이 나올 수 있습니다.

관련 정보