쉘 스크립트가 공백이나 다른 특수 문자에서 질식하는 이유는 무엇입니까?


284

또는 강력한 파일 이름 처리 및 셸 스크립트에서 전달되는 다른 문자열에 대한 소개 안내서입니다.

나는 대부분의 시간 동안 잘 작동하는 쉘 스크립트를 작성했다. 그러나 일부 입력 (예 : 일부 파일 이름)에서 질식합니다.

다음과 같은 문제가 발생했습니다.

  • 나는 공간을 포함하는 파일 이름을 가지고 hello world, 그것은 두 개의 별도의 파일로 처리 hello하고 world.
  • 두 개의 연속 공백이있는 입력 줄이 있고 입력 줄 하나가 줄었습니다.
  • 입력 행에서 선행 및 후행 공백이 사라집니다.
  • 입력에 문자 중 하나가 포함 된 경우 \[*?실제로는 파일 이름 인 일부 텍스트로 대체됩니다.
  • 입력에 아포스트로피 '(또는 큰 따옴표 ")가 있으며 그 시점 이후에는 문제가 생겼습니다.
  • 입력에 백 슬래시가 있습니다 (또는 : Cygwin을 사용하고 있으며 일부 파일 이름에는 Windows 스타일 \구분 기호가 있습니다).

무슨 일이 있고 어떻게 해결합니까?


16
shellcheck프로그램의 품질을 향상시키는 데 도움이됩니다.
aurelien

3
답변에 설명 된 보호 기술 외에도 대부분의 독자에게 분명하지만 명령 줄 도구를 사용하여 파일을 처리 할 때 파일에서 멋진 문자를 피하는 것이 좋습니다. 가능하면 처음에 이름을 쓰십시오.
bli


1
@bli 아니요. 버그만 발생시키는 데 시간이 오래 걸립니다. 오늘은 버그를 숨기고 있습니다. 이제는 나중에 코드와 함께 사용 된 모든 파일 이름을 알 수 없습니다.
Volker Siegel

먼저 매개 변수에 공백이 있으면 (명령 줄에서) 인용해야합니다. 그러나 전체 명령 행을 잡고 직접 구문 분석 할 수 있습니다. 두 칸은 한 칸으로 바뀌지 않습니다. 어떤 공간이라도 스크립트에 다음 변수임을 알려주므로 "echo $ 1 $ 2"와 같은 작업을 수행하면 스크립트가 하나의 공백을 넣습니다. 또한 "find (-exec)"를 사용하여 for 루프가 아닌 공백이있는 파일을 반복하십시오. 공간을보다 쉽게 ​​처리 할 수 ​​있습니다.
Patrick Taylor

답변:


352

변수 대체 및 명령 대체는 항상 큰 따옴표를 사용하십시오 "$foo"."$(foo)"

$foo인용 부호 를 사용하지 않으면 스크립트는 $(foo)공백 또는를 포함하는 입력 또는 매개 변수 (또는 명령 출력 )를 질식 \[*?시킵니다.

거기서, 당신은 독서를 멈출 수 있습니다. 자, 여기 몇 가지 더 있습니다 :

  • read내장을 사용 하여 한 줄씩 입력을 읽으려면readwhile IFS= read -r line; do …
    Plain을 사용하여read 백 슬래시와 공백을 특별히 처리합니다.
  • xargs피하십시오xargs . 당신이 사용해야하는 경우 xargs, 그것을 확인하십시오 xargs -0. 대신 find … | xargs, 좋아find … -exec … .
    xargs공백과 문자를 \"'특수하게 처리합니다 .

이 답변은 Bourne의 / POSIX 스타일의 쉘 (적용 sh, ash, dash, bash, ksh, mksh, yash...). Zsh 사용자는이를 생략하고 큰 따옴표가 필요한시기 의 끝을 읽어야합니다 . 대신에. 전체 내용을 이해하려면 표준 설명서 나 쉘 설명서를 읽으십시오 .


아래 설명에는 몇 가지 근사치 (대부분의 조건에서 적용되지만 주변 환경이나 구성에 영향을받을 수있는 설명)이 포함되어 있습니다.

왜 써야 "$foo"합니까? 따옴표 없이는 어떻게 되나요?

$foo"변수의 값을 가져 간다"는 의미는 아닙니다 foo. 훨씬 복잡한 것을 의미합니다.

  • 먼저 변수 값을 가져옵니다.
  • 필드 분할 : 해당 값을 공백으로 구분 된 필드 목록으로 취급하고 결과 목록을 작성하십시오. 변수가 포함되어 있으면, 예를 들면, foo * bar ​이 단계의 결과는 3 요소 목록 foo, *, bar.
  • 파일 이름 생성 : 각 필드를 glob, 즉 와일드 카드 패턴으로 취급하고이 패턴과 일치하는 파일 이름 목록으로 대체하십시오. 패턴이 파일과 일치하지 않으면 수정되지 않은 상태로 유지됩니다. 이 예에서는 foo현재 디렉토리의 파일 목록 다음에을 포함하는 목록이 표시됩니다 bar. 현재 디렉토리가 비어있는 경우, 결과는 foo, *, bar.

결과는 문자열 목록입니다. 셸 구문에는 목록 컨텍스트와 문자열 컨텍스트의 두 가지 컨텍스트가 있습니다. 필드 분할 및 파일 이름 생성은 목록 컨텍스트에서만 발생하지만 대부분의 경우입니다. 큰 따옴표는 문자열 컨텍스트를 구분합니다. 큰 따옴표로 묶은 전체 문자열은 분할되지 않는 단일 문자열입니다. (예외 : "$@"위치 매개 변수 목록으로 확장하는 경우 (예 : 위치 매개 변수가 세 개인 경우 "$@"와 동일합니다 "$1" "$2" "$3". $ *와 $ @의 차이점무엇입니까? )

$(foo)또는 로 명령을 바꾸는 경우에도 마찬가지 입니다 `foo`. 참고 `foo`로 인용 규칙은 이상하고 이식성 이 없으며 모든 현대 쉘 $(foo)은 직관적 인용 규칙을 제외하고는 절대적으로 동등한 것을 지원 합니다.

산술 대입의 결과도 동일한 확장을 거치지 만 확장 할 IFS수없는 문자 만 포함하기 때문에 일반적으로 문제 가되지 않습니다 ( 숫자 또는를 포함하지 않는다고 가정 -).

큰 따옴표는 언제 필요한가요?를 참조하십시오 . 따옴표를 생략 할 수있는 경우에 대한 자세한 내용은

이 모든 리 갈마 어가 발생하지 않는 한 항상 변수 및 명령 대체에 큰 따옴표를 사용해야합니다. 따옴표를 생략하면 오류뿐만 아니라 보안상의 허점이 생길 수 있습니다 .

파일 이름 목록을 어떻게 처리합니까?

myfiles="file1 file2"공백을 포함하여 파일 을 쓰는 경우 공백이 포함 된 파일 이름으로는 작동하지 않습니다. 유닉스 파일 이름은 /(항상 디렉토리 구분자 인) 이외의 문자 와 널 바이트 (대부분의 쉘이있는 쉘 스크립트에서는 사용할 수 없음)를 포함 할 수 있습니다.

와 같은 문제입니다 myfiles=*.txt; … process $myfiles. 이렇게하면 변수 myfiles에 5 자 문자열 이 포함되며 와일드 카드가 확장되었다고 *.txt쓸 때 $myfiles입니다. 이 예제는 스크립트를로 변경할 때까지 실제로 작동합니다 myfiles="$someprefix*.txt"; … process $myfiles. 경우 someprefix에 설정 final report이 작동하지 않습니다.

파일 이름과 같은 모든 종류의 목록을 처리하려면 목록을 배열에 넣으십시오. 여기에는 mksh, ksh93, yash 또는 bash가 필요합니다 (또는 모든 인용 문제가없는 zsh). 일반 POSIX 셸 (예 : 애쉬 또는 대시)에는 배열 변수가 없습니다.

myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"

Ksh88에는 다른 할당 구문이있는 배열 변수가 있습니다 set -A myfiles "someprefix"*.txt( ksh88 / bash 이식성이 필요한 경우 다른 ksh 환경에서 할당 변수 참조 ). Bourne / POSIX 스타일 쉘에는 하나의 단일 배열이 있으며, 여기에는 "$@"사용자가 설정 한 위치 매개 변수 배열이 set있으며 함수에 대해 로컬입니다.

set -- "$someprefix"*.txt
process -- "$@"

로 시작하는 파일 이름은 -어떻습니까?

관련 메모에서 파일 이름은 -(대시 / 빼기)로 시작할 수 있으며 대부분의 명령은 옵션을 나타내는 것으로 해석합니다. 변수 부분으로 시작하는 파일 이름이있는 경우 --위의 스 니펫에서 와 같이 파일 이름을 먼저 전달하십시오 . 이것은 명령이 옵션의 끝에 도달했음을 나타내므로 그 이후의 모든 항목은로 시작하더라도 파일 이름입니다 -.

또는 파일 이름이 이외의 다른 문자로 시작되도록 할 수 있습니다 -. 절대 파일 이름 은로 시작하고 상대 이름의 시작 부분에 /추가 할 수 있습니다 ./. 다음 스 니펫은 변수의 내용을 f로 시작하지 않는 동일한 파일을 참조하는 "안전한"방법으로 바꿉니다 -.

case "$f" in -*) "f=./$f";; esac

이 주제에 대한 마지막 참고 사항에서 일부 명령은 -이후에도 표준 입력 또는 표준 출력을 의미하는 것으로 해석 됩니다 --. 이라는 실제 파일을 참조해야 -하거나 그러한 프로그램을 호출하는 경우 stdin에서 읽거나 stdout에 쓰지 않으려면 -위와 같이 다시 작성하십시오 . "du-sh *"와 "du -sh ./*"의 차이점무엇입니까?를 참조하십시오 . 추가 토론을 위해.

변수에 명령을 어떻게 저장합니까?

“명령”은 명령 이름 (전체 경로가 있거나없는 실행 파일 이름, 함수 이름, 내장 또는 별명), 인수가있는 명령 이름 또는 쉘 코드의 세 가지를 의미 할 수 있습니다. 따라서 변수에 저장하는 방법에는 여러 가지가 있습니다.

명령 이름이 있으면 저장하고 평소처럼 큰 따옴표와 함께 변수를 사용하십시오.

command_path="$1"

"$command_path" --option --message="hello world"

인수가있는 명령이있는 경우 문제는 위의 파일 이름 목록과 동일합니다. 이는 문자열이 아니라 문자열 목록입니다. 공백을 사이에두고 하나의 문자열에 인수를 넣을 수는 없습니다. 그렇게하면 인수의 일부인 공백과 인수를 구분하는 공백의 차이를 알 수 없기 때문입니다. 쉘에 배열이 있으면 사용할 수 있습니다.

cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

배열없이 쉘을 사용한다면 어떨까요? 위치 매개 변수를 수정하지 않아도 여전히 위치 매개 변수를 사용할 수 있습니다.

set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"

방향 전환, 파이프 등 복잡한 쉘 명령을 저장해야하는 경우 어떻게해야합니까? 또는 위치 매개 변수를 수정하지 않으려는 경우? 그런 다음 명령이 포함 된 문자열을 작성하고 eval내장을 사용할 수 있습니다 .

code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"

의 정의에서 중첩 된 따옴표를 참고 code: 작은 따옴표는 '…'변수의 값이 있도록 리터럴 문자열을 구분하는 code문자열입니다 /path/to/executable --option --message="hello world" -- /path/to/file1. eval내장은 스크립트에서 나타난 것처럼 인수로 전달 된 문자열을 구문 분석하는 쉘을 알려줍니다, 그래서 그 시점에서 따옴표 및 파이프 등, 구문 분석

사용 eval이 까다 롭습니다. 언제 구문 분석 될지 신중하게 생각하십시오. 특히 파일 이름을 코드에 넣을 수는 없습니다. 소스 코드 파일에있는 것처럼 파일 이름을 인용해야합니다. 그렇게 할 직접적인 방법은 없습니다. 같은 뭔가 code="$code $filename"파일 이름에 쉘 특수 문자가 포함 된 경우 휴식 (공간, $, ;, |, <, >, 등). code="$code \"$filename\""아직도에 끊는다 "$\`. 심지어 code="$code '$filename'"파일 이름이 포함 된 경우 나누기 '. 두 가지 해결책이 있습니다.

  • 파일 이름 주위에 따옴표 레이어를 추가하십시오. 가장 쉬운 방법은 주위에 작은 따옴표를 추가하고 작은 따옴표를로 바꾸는 것 '\''입니다.

    quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
    code="$code '${quoted_filename%.}'"
    
  • 코드 내에서 변수 확장을 유지하여 코드 조각이 빌드 될 때가 아니라 코드가 평가 될 때 조회되도록합니다. 이것은 더 간단하지만 코드가 실행될 때 변수가 여전히 같은 값으로 주위에있는 경우에만 작동합니다 (예 : 코드가 루프에 빌드 된 경우).

    code="$code \"\$filename\""

마지막으로 코드를 포함하는 변수가 정말로 필요합니까? 코드 블록에 이름을 지정하는 가장 자연스러운 방법은 함수를 정의하는 것입니다.

무슨 일이야 read?

이없는 -r경우 read연속 행을 허용합니다. 이것은 단일 논리 입력 행입니다.

hello \
world

read입력 행을 문자로 구분 된 필드로 분리합니다 $IFS(없이 -r백 슬래시는 이스케이프 처리). 입력 세 단어를 포함하는 라인 인 경우에는 예를 들어, read first second third설정 first, 입력 단어의 처음에 second두 번째 워드와 third세번째 단어로. 단어가 더 있으면 마지막 변수에는 이전 단어를 설정 한 후 남은 모든 것이 포함됩니다. 선행 및 후행 공백이 잘립니다.

IFS빈 문자열로 설정 하면 트리밍을 피할 수 있습니다. IFS = 대신 왜 IFS = 읽기가 자주 사용 되는가?를 참조하십시오 . 읽는 동안 ..`? 더 긴 설명을 위해.

무슨 일이야 xargs?

입력 형식 xargs은 공백으로 구분 된 문자열이며 선택적으로 작은 따옴표 나 큰 따옴표로 묶을 수 있습니다. 이 형식은 표준 도구로 출력되지 않습니다.

xargs -L1또는 xargs -l줄에 대한 입력 은 거의 줄 목록이지만 줄 끝에 공백이 있으면 다음 줄은 연속 줄입니다.

당신이 사용할 수있는 xargs -0적용 가능한 (그리고 가능한 경우 : GNU (리눅스, Cygwin에서), 비지 박스, BSD, OSX하지만 POSIX 아닙니다). null 바이트는 대부분의 데이터, 특히 파일 이름에 나타날 수 없기 때문에 안전합니다. 널로 구분 된 파일 이름 목록을 생성하려면를 사용하십시오 find … -print0(또는 find … -exec …아래 설명 된대로 사용할 수 있음 ).

찾은 파일은 어떻게 처리 find합니까?

find  -exec some_command a_parameter another_parameter {} +

some_command외부 명령이어야하며 셸 함수 나 별칭 일 수 없습니다. 파일을 처리하기 위해 쉘을 호출해야하는 경우 sh명시 적으로 호출하십시오 .

find  -exec sh -c '
  for x do
    … # process the file "$x"
  done
' find-sh {} +

다른 질문이 있습니다

이 사이트 에서 태그 또는 또는 . (“자세히 알아보기…”를 클릭하면 몇 가지 일반적인 팁과 손으로 선택하는 일반적인 질문 목록을 볼 수 있습니다.) 검색했지만 답변을 찾을 수없는 경우, 문의하십시오 .


6
@ John1024 이것은 GNU 기능 일 뿐이므로“표준 도구 없음”을 고수하겠습니다.
Gilles

2
또한 (sh 에뮬레이션에서도) 및를 제외하고 ( 일부 쉘 $(( ... ))에서도) 따옴표가 필요합니다 . $[...]zshmksh
Stéphane Chazelas

3
즉, 참고 xargs -0POSIX 없습니다. FreeBSD를 제외하고 xargs, 당신은 일반적으로 xargs -r0대신에 원합니다 xargs -0.
Stéphane Chazelas

2
@ John1024는 아니요 ls --quoting-style=shell-always와 호환되지 않습니다 xargs. 시도touch $'a\nb'; ls --quoting-style=shell-always | xargs
Stéphane Chazelas

3
또 다른 멋진 (GNU 전용) 기능은 xargs -d "\n"당신이 예를 실행할 수 있도록 locate PATTERN1 |xargs -d "\n" grep PATTERN2일치하는 파일 이름을 검색 PATTERN1 콘텐츠 일치와 PATTERN2를 . GNU가 없다면 다음과 같이 할 수 있습니다locate PATTERN1 |perl -pne 's/\n/\0/' |xargs -0 grep PATTERN1
Adam Katz

26

Gilles의 답변은 훌륭하지만 그의 주요 요점에서 문제를 제기합니다.

변수 대체 및 명령 대체에는 항상 큰 따옴표를 사용하십시오 : "$ foo", "$ (foo)"

단어 분할을 수행하는 Bash와 같은 쉘로 시작할 때 물론 안전한 조언은 항상 따옴표를 사용하는 것입니다. 그러나 단어 분리가 항상 수행되는 것은 아닙니다

§ 단어 분리

이 명령은 오류없이 실행될 수 있습니다

foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
case $foo in bar) echo bar ;; baz) echo baz ;; esac

사용자가이 동작을 채택하도록 권장하지는 않지만 단어 분리가 발생하는시기를 누군가가 확실하게 이해하면 인용 부호를 사용할시기를 스스로 결정할 수 있어야합니다.


19
내 답변에서 언급 했듯이 자세한 내용 은 unix.stackexchange.com/questions/68694/… 를 참조하십시오. “쉘 스크립트가 왜 질식합니까?”라는 질문에 주목하십시오. 이 사이트와 다른 곳에서 수년간의 경험을 통해 가장 일반적인 문제는 큰 따옴표가 누락되었습니다. "항상 큰 따옴표를 사용하십시오"는 "큰 따옴표를 사용하지만 항상 필요하지 않은 경우를 제외하고"보다 기억하기 쉽습니다.
Gilles

14
초보자는 규칙을 이해하기 어렵습니다. 예를 들어, foo=$bar괜찮지 만 export foo=$bar그렇지 env foo=$var않습니다 (적어도 일부 쉘에서는). 초보자를위한 조언 : 하고있는 일을 알지 못하는 이유가없는 한 항상 변수를 인용하십시오 .
Stéphane Chazelas

5
@StevenPenny 더 정확합니까? 따옴표로 인해 스크립트가 깨지는 합리적인 경우가 있습니까? 절반의 경우 따옴표를 사용해야 하고 다른 절반의 따옴표를 선택적으로 사용할 있는 상황에서는 "항상 따옴표를 항상 사용하십시오"라는 권장 사항은 사실이고 단순하며 덜 위험하기 때문에 고려해야합니다. 초보자에게 그러한 예외 목록을 가르치는 것은 필요하지 않은 따옴표를 혼동하고 대본을 깨뜨리고 더 많은 것을 배우도록 동기를 부여하기 때문에 효과가 없으며 (상황이 부족하고 기억하지 않을 것임) 비생산적인 것으로 잘 알려져 있습니다.
Peteris

6
내 $ 0.02는 모든 것을 인용하는 것이 좋습니다. 필요하지 않은 것을 잘못 인용하는 것은 무해하며, 실수로 그것을 인용하지 않으면 해 롭습니다. 따라서 정확히 단어 분리가 발생할 때의 복잡성을 이해하지 못하는 대부분의 쉘 스크립트 작성자에게는 필요한 경우에만 인용하는 것보다 모든 것을 인용하는 것이 훨씬 안전합니다.
godlygeek

5
@Peteris와 godlygeek : "따옴표로 인해 스크립트가 깨지는 합리적인 경우가 있습니까?" "합리적"의 정의에 따라 다릅니다. 스크립트가을 설정 criteria="-type f"하면 find . $criteria작동하지만 작동 find . "$criteria"하지 않습니다.
G-Man

22

지금까지 내가 아는 한, 거기는 이중 인용 확장 할 필요가있는 두 가지 경우가 있으며, 이러한 경우는 두 가지 특별한 쉘 매개 변수를 포함 "$@"하고 "$*"- 큰 따옴표로 묶여 때 다르게 확대 지정됩니다. 쉘 특정 배열 구현을 제외한 다른 모든 경우 에는 확장 동작이 구성 가능한 것입니다. 옵션이 있습니다.

물론 이것은 큰 따옴표를 피해야한다는 것은 아닙니다. 반대로 쉘이 제공해야 할 확장을 구분하는 가장 편리하고 강력한 방법 일 것입니다. 그러나 대안이 이미 전문적으로 설명 되었기 때문에 쉘이 가치를 확장 할 때 어떤 일이 발생하는지 논의하기에 좋은 곳이라고 생각합니다.

쉘은 그 마음과 영혼에 (그러한 사람들을 위해) 명령 통역사입니다-그것은 큰 대화 형과 같은 파서 sed입니다. 쉘 명령문이 공백 또는 이와 유사한 것에서 질식 하는 경우 쉘의 해석 프로세스, 특히 입력 명령문을 실행 가능한 명령으로 변환하는 방법 및 이유를 완전히 이해하지 못했을 가능성이 높습니다. 쉘의 임무는 다음과 같습니다.

  1. 입력을 수락

  2. 토큰 화 된 입력 단어 로 올바르게 해석 및 분할

    • 입력 된 단어 등의 쉘 구문 항목이다 $word또는echo $words 3 4* 5

    • 단어 는 항상 공백으로 나뉩니다. 구문 일뿐입니다. 그러나 입력 파일의 쉘에 문자 그대로의 공백 문자 만 제공됩니다.

  3. 필요한 경우 여러 필드 로 확장

    • 필드단어 확장의 결과입니다 . 최종 실행 명령을 구성합니다.

    • "$@", $IFS 필드 분할경로 이름 확장을 제외하고 입력 단어 는 항상 단일 필드로 평가되어야 합니다 .

  4. 그런 다음 결과 명령을 실행합니다

    • 대부분의 경우 해석 결과를 어떤 형태로든 전달해야합니다.

사람들은 종종 껍질이 접착제 라고 말합니다. 이것이 사실이라면, 그것이 붙는 것은 하나의 프로세스 또는 다른 프로세스에 대한 인수 또는 필드의 목록입니다 exec. 대부분의 쉘은 NUL바이트를 잘 처리하지 못합니다 . 이것은 이미 분할되어 있기 때문입니다. 쉘은 exec 많은 것을해야하며 NUL, exec시간에 시스템 커널에 전달 되는 구분 된 인수 배열로 이를 수행해야합니다 . 쉘의 분리 문자를 분리 된 데이터와 혼합하려면 쉘이이를 망칠 수 있습니다. 대부분의 프로그램과 마찬가지로 내부 데이터 구조는 해당 구분 기호를 사용합니다. zsh특히, 이것을 망치지 않습니다.

그리고 그 위치 $IFS$IFS항상 존재 합니다. 마찬가지로 설정 가능한 셸 매개 변수는 셸이 셸 확장을 단어 에서 필드로 나누는 방법, 특히 해당 필드 가 어떤 값 을 구분해야하는지 정의합니다. $IFS이외 분리 셸 확장 분할 NUL- 또는 다른 말로 팽창으로 인한 쉘 대체품 바이트는 값에 일치 $IFS하여 NUL내부 데이터 어레이이다. 이런 식으로 보면 모든 필드 분할 쉘 확장이 $IFS구분 된 데이터 배열 임을 알 수 있습니다 .

큰 따옴표로 수행 할 수있는 아직 구분 되지 않은 확장 $IFS구분 한다는 것을 이해하는 것이 중요 합니다. 당신이 확장을 인용 할 때 당신은 그것을 머리 에서 최소한 그것의 가치의 꼬리로 구분합니다 . 이 경우 구분할 필드가 없으므로 적용되지 않습니다. 실제로, 큰 따옴표로 묶인 확장은 비어있는 값으로 설정된 경우 따옴표없는 확장 과 동일한 필드 분할 동작을 나타냅니다 ."$IFSIFS=

인용하지 않는 한, $IFS그 자체는 $IFS분리 된 쉘 확장입니다. 기본값은 지정된 값으로 설정 <space><tab><newline>됩니다.이 세 가지 값은 모두 안에 포함 된 경우 특별한 속성을 나타냅니다 $IFS. 확장 발생$IFS단일 필드 로 평가하기 위해 다른 값을 지정하는 반면 , 이 3 개 중 하나 인 공백 문자 는 확장 시퀀스 당 단일 필드를 제거하도록 지정 되며 선행 / 트레일 링 시퀀스는 완전히 제거됩니다. 이것은 아마도 예제를 통해 이해하는 것이 가장 쉽습니다.$IFS

slashes=///// spaces='     '
IFS=/; printf '<%s>' $slashes$spaces
<><><><><><     >
IFS=' '; printf '<%s>' $slashes$spaces
</////>
IFS=; printf '<%s>' $slashes$spaces
</////     >
unset IFS; printf '<%s>' "$slashes$spaces"
</////     >

그러나 그것은 단지 $IFS단어 분리 또는 공백입니다 . 그래서 특별한 문자는 무엇입니까?

쉘은 기본적으로 인용되지 않은 특정 토큰 (예 : ?*[다른 곳에서 언급 한 것처럼) 이 목록에서 나타날 때 여러 필드로 확장 됩니다. 이것을 경로 이름 확장 또는 globbing이라고 합니다. 그 이후에 발생으로는, 믿을 수 없을만큼 유용한 도구이며, 현장에서 분할 이 영향을받지 않습니다 쉘의 구문 분석-위해 $ IFS - 필드 경로명 확장에 의해 발생 여부에 관계없이 파일 이름 자신의 머리 / 꼬리에 구분됩니다 내용은 현재에있는 모든 문자를 포함합니다 $IFS. 이 동작은 기본적으로 설정되어 있지만 그렇지 않으면 매우 쉽게 구성됩니다.

set -f

이것은 쉘 이 glob 하지 않도록 지시합니다 . 현재 쉘이 다른 새로운 쉘 프로세스로 대체되거나 다른 경우와 같이 설정이 취소되기 전까지는 경로 이름 확장이 발생하지 않습니다.

set +f

... 쉘에 발행됩니다. 이중 $IFS 분할필드 분할 과 마찬가지로 확장마다이 전역 설정을 불필요하게 만듭니다. 그래서:

echo "*" *

... 경로 확장은 현재 가능성이 인수에 따라 매우 다른 결과를 얻을 활성화 된 경우 - 첫 번째는 그 문자 값으로 확장됩니다로 (전혀, 말을하는 것입니다 단일 별표 문자) 만 같은 두 번째 현재 작업 디렉토리에 일치하는 파일 이름이 포함되어 있지 않으면 거의 모든 파일 이름 과 일치합니다 . 그러나 당신이 할 경우 :

set -f; echo "*" *

... 두 인수에 대한 결과는 동일합니다 *.이 경우에는 확장되지 않습니다.


나는 실제로 @ StéphaneChazelas에게 (대부분) 도움보다 일을 혼란스럽게한다는 데 동의하지만 개인적으로 도움이된다는 것을 알았습니다. 이제 IFS실제로 작동 하는 방법에 대한 더 나은 아이디어 (및 일부 예)가 있습니다. 내가 하지 않는 얻을 것은 왜이다 으로 설정하는 것은 좋은 생각이 될 IFS기본이 아닌 다른 뭔가.
와일드 카드

1
@Wildcard-필드 구분 기호입니다. 변수에 여러 필드로 확장하려는 값이 있으면 그 값을 분할합니다 $IFS. 그런 다음 cd /usr/bin; set -f; IFS=/; for path_component in $PWD; do echo $path_component; done인쇄합니다 . 첫 번째 필드는 널 필드 이므로 비어 있습니다. path_components는 줄 바꿈 또는 공백 또는 기타를 가질 수 있습니다-구성 요소가 기본값이 아닌 분리되어 있기 때문에 중요 하지 않습니다. 어쨌든 사람들은 항상 그 일을합니다. 당신의 쉘도 그렇게합니다\nusr\nbin\necho//awk
mikeserv

3

파일 이름에 공백이 있고 디렉토리 이름에 공백이있는 큰 비디오 프로젝트가 있습니다. find -type f -print0 | xargs -0여러 목적과 다양한 셸에서 작동 하지만 사용자 정의 IFS (입력 필드 구분 기호)를 사용하면 bash를 사용하는 경우 더 많은 유연성을 얻을 수 있습니다. 아래 스 니펫은 bash를 사용하고 IFS를 개행으로 설정합니다. 파일 이름에 줄 바꿈이 없다면 :

(IFS=$'\n'; for i in $(find -type f -print) ; do
    echo ">>>$i<<<"
done)

IFS의 재정의를 분리하기 위해 parens를 사용하는 것에 주목하십시오. IFS를 복구하는 방법에 대한 다른 게시물을 읽었지만 더 쉽습니다.

또한 IFS를 줄 바꿈으로 설정하면 셸 변수를 미리 설정하고 쉽게 인쇄 할 수 있습니다. 예를 들어 개행 문자를 구분 기호로 사용하여 변수 V를 점차적으로 증가시킬 수 있습니다.

V=""
V="./Ralphie's Camcorder/STREAM/00123.MTS,04:58,05:52,-vf yadif"
V="$V"$'\n'"./Ralphie's Camcorder/STREAM/00111.MTS,00:00,59:59,-vf yadif"
V="$V"$'\n'"next item goes here..."

그리고 이에 상응하여 :

(IFS=$'\n'; for v in $V ; do
    echo ">>>$v<<<"
done)

이제 echo "$V"큰 따옴표를 사용하여 줄 바꿈을 출력하여 V 설정을 "목록"할 수 있습니다 . ( 설명 을 위해이 스레드$'\n'기여합니다.)


3
그러나 줄 바꿈 문자 나 glob 문자가 포함 된 파일 이름에는 여전히 문제가 있습니다. 참조 : 왜 루프 오버 루프가 나쁜 결과입니까? . 를 사용 zsh하면 사용 IFS=$'\0'하고 사용할 수 있습니다 -print0( zsh확장시 globbing을 수행하지 않으므로 glob 문자는 문제가되지 않습니다).
Stéphane Chazelas

1
공백이 포함 된 파일 이름에는 작동하지만 잠재적으로 적대적인 파일 이름이나 실수로 "무의미한"파일 이름에는 작동하지 않습니다. 을 추가하여 와일드 카드 문자를 포함하는 파일 이름 문제를 쉽게 해결할 수 있습니다 set -f. 반면에 줄 바꿈이 포함 된 파일 이름으로 접근 방식이 근본적으로 실패합니다. 파일 이름 이외의 데이터를 처리 할 때 빈 항목도 실패합니다.
Gilles

맞아, 내주의 사항은 파일 이름의 줄 바꿈으로 작동하지 않는다는 것입니다. 그러나 나는 우리가 광기를 부끄러워하는 선을 그려야한다고 믿는다 ;-)
Russ

그리고 왜 이것이 공감대를 받았는지 잘 모르겠습니다. 공백이있는 파일 이름을 반복하는 데는 매우 합리적인 방법입니다. -print0을 사용하려면 xargs가 필요하며 해당 체인을 사용하기 어려운 것이 있습니다. 누군가 내 답변에 동의하지 않아서 죄송하지만 그 이유는 없습니다.
Russ

0

위에서 언급 한 모든 보안 관련 사항을 고려하고 변수를 신뢰하고 변수를 제어한다고 가정하면 확장을 사용하여 공백을 사용하여 여러 경로를 사용할 수 eval있습니다. 그러나 조심하십시오!

$ FILES='"a b" c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
$ FILES='a\ b c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.