루프가 있는 변수 IFS 및 목록 파일에 대한 결과가 다릅니다.

루프가 있는 변수 IFS 및 목록 파일에 대한 결과가 다릅니다.

현재 디렉터리와 그 하위 디렉터리에 있는 파일 목록을 얻고 싶습니다(한 줄 스크립트를 사용하고 싶습니다).

IFS=$(echo -en "\n\b");
for FILE in $(find -type f); do echo "$FILE"; done

일반적으로 예상대로 작동하지만 최근 내 파일 목록은 다음과 같습니다.

file_.doc
파일_0.doc
파일_[2006_02_25].doc
파일_[2016_06_16].odt
파일_[2016_06_16].pdf
파일_[16-6-2006].doc
파일_.pdf
파일_ 4-4-2006.doc

출력은 다음과 같습니다

./file_.doc                                                                                                                                                 
./file_0.doc                                                                                                                                                
./file_0.doc
./file_[2016_06_16].odt
./file_[2016_06_16].pdf
./file_0.doc
./file_.pdf
./file_ 4-4-2006.doc

변수 IFS를 다음과 같이 변경하면:

IFS=$(echo -en "\n");

그러면 출력은 다음과 같이 수정됩니다.

./file_.doc
./file_0.doc
./file_[2006_02_25].doc
./file_[2016_06_16].odt
./file_[2016_06_16].pdf
./file_[16-6-2006].doc
./file_.pdf
./file_ 4-4-2006.doc

내가 읽은 '\b'것은필요한, 그리고 해결책을 찾았습니다사용하다 printf, 교체하다echo.

내 질문은 다음과 같습니다

1) 이러한 출력이 어떻게 다른지 설명할 수 있습니까?

2) 위의 솔루션을 사용하는 것에 대한 printf대안이 있습니까 echo -en "\n\b"?

답변1

명령 대체의 출력은 단어 분할의 영향을 받습니다(설정을 통해 이를 처리할 수 있음 IFS).그리고파일 이름 와일드카드. 구성 [abc]은 평소와 같이 "모든 문자 a, b, c"와 일치하며 [2006_02_25].doc이는 와 일치합니다 0.doc.


Bash/ksh/zsh에서는 바이너리를 사용하여 디렉터리 트리(현재 디렉터리뿐만 아니라 재귀적으로)의 모든 파일을 가져올 수 있습니다. 그러면 예제와 동일한 파일을 찾아야 합니다.

shopt -s globstar      # in Bash
# set -o globstar      # in ksh
for file in **/* ; do
    [[ -f $file ]] || continue     # check it's a regular file, like find -type f
    ...
done

틀림없이,find 강력하기 때문에 조건이 많은 경우 사용하기가 더 쉬울 수도 있습니다. 이렇게 하면 set -f문제를 해결하는 것 외에도 파일 이름 글로빙도 비활성화해야 합니다 IFS.

set -f
IFS=$'\n'
for file in $(find -type f -some -other -conditions) ; do
    ...
done

또는 while read프로세스 대체와 함께 루프를 사용하십시오.

while IFS= read -r file ; do 
    ...
done < <(find -type f -some -other -conditions)

(위와 비슷 find ... | while ...하지만 파이프의 마지막 부분이 서브쉘에서 실행되는 문제를 우회합니다.)

두 가지 모두 파일 이름에 개행 문자가 포함되어 있지 않다고 가정합니다 find. 왜냐하면 $(..)파일 이름은 .

적어도 Bash에는 실제로 파일 이름에 줄 바꿈을 적용하는 방법도 있습니다. 구분 기호를 read빈 문자열로 설정하면 NUL 바이트가 구분 기호로 효과적으로 사용됩니다. 그래서:

while IFS= read -d '' -r file ; do 
    ...
done < <(find -type f -some -other -conditions -print0)

read하지만 입력을 중단하지 않고 작동 하려면 이러한 모든 옵션이 실제로 필요하기 때문에 짜증나기 시작합니다 .


... 에 관해서는 IFS설정이 빈 문자열로 IFS=$(echo -en "\n");설정됩니다 (명령 대체가 후행 줄 바꿈을 먹기 때문에).IFS아니요나뉘다. 이 경우 find한 줄씩 가져오는 것이 아니라 한 번에 모두 가져오기 때문에 출력이 올바른 것 같습니다 . 전체 여러 줄 문자열이 어떤 파일 이름과도 일치하지 않고 그대로 전달되므로 파일 이름 글로빙 문제도 가려집니다.

루프 값을 인쇄하는 것 외에 다른 작업을 수행하면 차이점을 확인할 수 있습니다. 몇 가지 구분 기호를 추가해 보세요.

IFS=$(echo -en "\n")       # same as IFS=
for FILE in $(find -type f); do echo "<$FILE>"; done

IFS가장 단순한 표준 쉘이 아닌 다른 쉘에서 개행 문자를 설정하는 가장 쉬운 방법은 입니다 IFS=$'\n'.

답변2

이렇게 하지 마십시오 $( find ... ). 파일 이름 생성(와일드카드 지정)을 호출하고 일부 파일 이름은 다른 파일 이름과 일치하는 와일드카드 패턴으로 해석됩니다. 예를 들어, Pattern file_[2006_02_25].docfile_[16-6-2006].docmatch 가 file_0.doc있기 때문에 이 두 패턴 대신 이 파일 이름이 나타납니다.

또한 명령 대체의 명령이 모든 경로 이름을 생성할 때까지 루프는 반복을 시작하지 않습니다 find. 이는 일반적인 경우 상당한 메모리를 차지할 수 있으며 실제로는 그렇지 않습니다.우아한.

대신 다음을 사용 find하고 수정하지 마세요 IFS.

find . type -f -print

이러한 파일에 대해 다른 작업을 수행하려면 다음 위치에서 수행할 수 있습니다 -exec.

find . -type f -exec sh -c 'printf "Found the file %s\n" "$@"' sh {} +

현재 디렉터리의 파일만 처리하려면 다음을 수행하면 됩니다.

for name in *; do
    printf 'Found the name %s\n' "$name"
done

관련된:

관련 정보