IFS 이해


71

이 사이트의 다음 스레드와 StackOverflow는 IFS작동 방식 을 이해하는 데 도움이되었습니다 .

그러나 여전히 몇 가지 간단한 질문이 있습니다. 나는 미래의 독자들에게 도움이 될 것이라고 생각하기 때문에 같은 게시물에 질문하기로 결정했습니다.

Q1. IFS일반적으로 "필드 분할"의 맥락에서 논의됩니다. 가 필드 분할은 같은 단어 분할 ?

Q2 : POSIX 사양 다음과 같이 말합니다 .

IFS 값이 널이면 필드 분할이 수행되지 않습니다.

null IFS=로 설정 한 것과 동일하게 설정 IFS되어 있습니까? 이것이 empty string너무 설정되어 있다는 의미 입니까?

Q3 : POSIX 사양 에서 다음을 읽었 습니다.

IFS가 설정되어 있지 않으면 쉘은 IFS 값이 다음과 같이 작동합니다. <space>, <tab> and <newline>

기본값을 복원하고 싶다고 가정 해보십시오 IFS. 어떻게합니까? (보다 구체적으로 어떻게 참조 할 <tab><newline>?)

Q4 : 마지막으로이 코드는 어떻게 됩니까?

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file

첫 줄을 다음과 같이 바꾸면

while read -r line # Use the default IFS value

또는

while IFS=' ' read -r line

답변:


28
  1. 예, 동일합니다.
  2. 예.
  3. bash 및 유사한 쉘에서 다음과 같은 작업을 수행 할 수 IFS=$' \t\n'있습니다. 그렇지 않으면을 사용하여 리터럴 제어 코드를 삽입 할 수 있습니다 [space] CTRL+V [tab] CTRL+V [enter]. 그러나이 작업을 수행하려는 경우 다른 변수를 사용하여 이전 IFS값 을 임시로 저장 한 다음 나중에 복원하거나 var=foo command구문 을 사용하여 한 명령에 대해 임시로 재정의하는 것이 좋습니다.
    • 첫 번째 코드 스 니펫은 $line단어 분리를 수행 할 필드 구분 기호가 없으므로 전체 행을 그대로 읽습니다 . 그러나 많은 쉘이 문자열을 저장하기 위해 cstring을 사용하기 때문에, NUL의 첫 번째 인스턴스는 여전히 조기 종료되는 것처럼 보일 수 있습니다.
    • 두 번째 코드 스 니펫은 입력의 정확한 사본을에 넣지 않을 수 있습니다 $line. 예를 들어 연속 된 필드 구분자가 여러 개인 경우 첫 번째 요소의 단일 인스턴스로 만들어집니다. 이것은 종종 주변 공백의 손실로 인식됩니다.
    • 세 번째 코드 스 니펫은 두 번째 코드와 동일하게 수행됩니다 (단, 일반 스페이스, 탭 또는 개행 문자는 제외).

3
Q2에 대한 답변이 잘못되었습니다. 비어있는 IFS것과 설정되지 않은 IFS것은 매우 다릅니다. Q4에 대한 대답은 부분적으로 잘못되었습니다. 내부 구분 기호는 여기에 닿지 않고 앞뒤 구분자 만 만집니다.
Gilles

3
@Gilles : 2 분기에 주어진 3 개의 명칭 중 어느 것도 설정되지 않은 IFS것을 의미하지 않습니다 IFS=.
Stéphane Gimenez

@Gilles 2 분기에 나는 그들이 같은 말을 한 적이 없다. 그리고 내부 분리기는 다음과 같이 터치됩니다 IFS=' ' ; foo=( bar baz qux ) ; echo "${#foo[@]}". (또는 무엇입니까? 여기에 공간 구분 기호가 여러 개 있어야하므로 엔진이 계속 제거합니다).
Chris Down

2
@ StéphaneGimenez, Chris : 아, 맞습니다. Q2에 대해 죄송합니다. 질문을 잘못 읽었습니다. Q4의 경우는 다음과 같습니다 read. 마지막 변수는 마지막 구분 기호를 제외하고 남은 모든 것을 잡고 내부 구분 기호를 내부에 둡니다.
Gilles

1
Gilles는 읽기로 제거되지 않은 공간에 대해 부분적으로 정확합니다. 자세한 내용은 내 대답을 읽으십시오.

22

Q1 : 그렇습니다. "필드 분할"과 "단어 분할"은 동일한 개념에 대한 두 가지 용어입니다.

Q2 : 그렇습니다. IFS설정되지 않은 경우 (예 :) , 공백, 탭 및 줄 바꿈 으로 설정 한 unset IFS것과 같습니다 . 빈 값으로 설정된 경우 (즉, 여기서 "널 (null)"이 의미하는 경우 (예 : 또는 또는 )) 필드 분할이 전혀 수행되지 않습니다 (및 의 첫 번째 문자를 일반적으로 사용하는 공백 문자를 사용함).IFS$' \t\n'IFSIFS=IFS=''IFS=""$*$IFS

Q3 : 기본 IFS동작 을 원하면을 사용할 수 있습니다 unset IFS. IFS이 기본값으로 명시 적 으로 설정 하려면 리터럴 문자 공백, 탭, 줄 바꿈을 작은 따옴표로 묶을 수 있습니다. ksh93, bash 또는 zsh에서는을 사용할 수 있습니다 IFS=$' \t\n'. 소스 파일에 리터럴 탭 문자를 사용하지 않으려면 다음을 사용할 수 있습니다.

IFS=" $(echo t | tr t \\t)
"

Q4 : IFS빈 값으로 read -r line설정 line하면 종료 개행을 제외한 전체 행으로 설정 됩니다. 을 사용 IFS=" "하면 줄의 시작과 끝에 공백이 잘립니다. 기본값 인 IFS으로 탭과 공백이 잘립니다.


2
Q2는 부분적으로 잘못되었습니다. IFS가 비어 있으면 "$ *"는 구분 기호없이 결합됩니다. ( $@와 같이 목록이 아닌 컨텍스트에서 쉘 사이에는 약간의 변형이 있습니다 IFS=; var=$@). IFS가 비어 있으면 단어 분할이 수행되지 않지만 $ var이 비어 있고 globbing이 여전히 적용되는 경우 $ var가 빈 인수 대신 인수가없는 확장으로 계속 확장 되므로 변수를 인용해야합니다 ( globbing 사용 안함)
Stéphane Chazelas

13

Q1. 필드 분할.

필드 분할은 단어 분할과 동일합니까?

예, 둘 다 같은 생각을 가리 킵니다.

Q2 : IFS 는 언제 null 입니까?

IFS=''빈 문자열과 동일한 null로 설정 합니까?

예, 세 가지 모두 동일합니다. 필드 / 단어 분할을 수행하지 않아야합니다. 또한 이것은 (와 마찬가지로 echo "$*") 인쇄 필드에 영향을 미치며 모든 필드는 공간없이 함께 연결됩니다.

Q3 : (파트 a) Unset IFS.

POSIX 사양 에서 다음을 읽었습니다 .

IFS가 설정되어 있지 않으면 쉘은 IFS 값이 <space> <tab> <newline> 인 것처럼 동작합니다 .

다음과 정확히 동일합니다.

를 사용하면 unset IFS쉘은 IFS가 기본값 인 것처럼 동작합니다.

이는 '필드 분할'이 기본 IFS 값과 정확히 동일하거나 설정되지 않음을 의미합니다.
그렇다고 IFS가 모든 조건에서 동일한 방식으로 작동한다는 의미는 아닙니다. 보다 구체적으로 실행 OldIFS=$IFS하면 var OldIFS가 기본값이 아닌 null로 설정됩니다. 이와 같이 IFS를 다시 설정하려고하면 IFS=OldIFSIFS가 null로 설정되어 이전과 같이 설정되지 않은 상태로 유지됩니다. 조심해 !!.

Q3 : (파트 b) IFS 복원.

IFS 값을 기본값으로 복원하는 방법 IFS의 기본값을 복원하고 싶다고 가정 해보십시오. 어떻게합니까? (더 구체적으로, <tab><newline>을 어떻게 참조합니까?)

zsh, ksh 및 bash (AFAIK)의 경우 IFS를 다음과 같이 기본값으로 설정할 수 있습니다.

IFS=$' \t\n'        # works with zsh, ksh, bash.

완료, 당신은 다른 것을 읽을 필요가 없습니다.

그러나 sh에 대해 IFS를 다시 설정해야하는 경우 복잡해질 수 있습니다.

단점을 제외하고 가장 쉬운 방법부터 완료 해 봅시다 (복잡성 제외).

IFS를 설정 해제합니다.

우리는 단지 unset IFS(위의 Q3 부분 a를 읽으십시오).

스왑 문자.

이 문제를 해결하려면 tab과 newline의 값을 바꾸면 IFS의 값을 더 간단하게 설정 한 다음 동일한 방식으로 작동합니다.

IFS를 <space> <newline> <tab>으로 설정하십시오 .

sh -c 'IFS=$(echo " \n\t"); printf "%s" "$IFS"|xxd'      # Works.

3. 간단한? 해결책:

IFS를 올바르게 설정해야하는 하위 스크립트가있는 경우 언제든지 수동으로 작성할 수 있습니다.

IFS = '   
'

어디 수동으로 입력 순서가 있었다 : IFS='spacetabnewline'실제로 제대로 (당신이 확인해야하는 경우,이 대답을 편집) 위의 입력 된 순서. 그러나 브라우저가 공백을 쥐어 짜거나 숨기기 때문에 브라우저에서 복사 / 붙여 넣기가 중단됩니다. 위에서 설명한대로 코드를 공유하기가 어렵습니다.

완벽한 솔루션.

안전하게 복사 할 수있는 코드를 작성하려면 일반적으로 명확한 인쇄 가능한 이스케이프가 필요합니다.

예상 값을 "생성하는"코드가 필요합니다. 그러나 개념적으로 정확 하더라도이 코드는 후행을 설정하지 않습니다 \n.

sh -c 'IFS=$(echo " \t\n"); printf "%s" "$IFS"|xxd'      # wrong.

대부분의 셸 에서 확장 후 모든 줄 바꿈 $(...)또는 `...`명령 대체가 제거 되기 때문에 발생합니다 .

우리는 sh에 대한 트릭 을 사용해야합니다 .

sh -c 'IFS="$(printf " \t\nx")"; IFS="${IFS%x}"; printf "$IFS"|xxd'  # Correct.

다른 방법은 bash에서 환경 값으로 IFS를 설정 한 다음 (예 : 환경을 통해 IFS가 설정되도록 허용하는 버전) sh를 호출하는 것입니다.

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'

간단히 말해 sh는 IFS를 기본값으로 재설정하는 것이 매우 이상한 모험입니다.

Q4 : 실제 코드에서 :

마지막으로,이 코드는 어떻게 :

while IFS= read -r line
do
    echo $line
done < /path_to_text_file

첫 줄을 다음과 같이 바꾸면

while read -r line # Use the default IFS value

또는

while IFS=' ' read -r line

첫째 : echo $line(var NOT 인용 된)이 Porpouse에 있는지 여부는 알 수 없습니다. 읽기에는없는 두 번째 레벨의 '필드 분할'을 소개합니다. 둘 다 대답하겠습니다. :)

이 코드를 사용하면 확인할 수 있습니다. 유용한 xxd 가 필요합니다 :

#!/bin/ksh
# Correctly set IFS as described above.
defIFS="$(printf " \t\nx")"; defIFS="${defIFS%x}";
IFS="$defIFS"
printf "IFS value: "
printf "%s" "$IFS"| xxd -p

a='   bar   baz   quz   '; l="${#a}"
printf "var value          : %${l}s-" "$a" ; printf "%s\n" "$a" | xxd -p

printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x--          : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf 'Values      quoted :\n' ""  # With values quoted:
printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null    quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS default quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf '%s\n' "Values unquoted :"   # Now with values unquoted:
printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x-- unquoted : "
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null  unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS defau unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

나는 얻다:

$ ./stackexchange-Understanding-IFS.sh
IFS value: 20090a
var value          :    bar   baz   quz   -20202062617220202062617a20202071757a2020200a
IFS --x--          :    bar   baz   quz   -20202062617220202062617a20202071757a202020
Values      quoted :
IFS null    quoted :    bar   baz   quz   -20202062617220202062617a20202071757a202020
IFS default quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS unset   quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS space   quoted :       bar   baz   quz-62617220202062617a20202071757a
Values unquoted :
IFS --x-- unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS null  unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS defau unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS unset unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS space unquoted : bar, baz, quz, 6261722c62617a2c71757a2c

첫 번째 값은 올바른 값입니다 IFS='spacetabnewline'

다음 줄은 var $a가 가진 모든 16 진 값이며, 각 읽기 명령에 주어질 때 줄 바꿈 '0a'입니다.

IFS가 null 인 다음 행은 '필드 분할'을 수행하지 않지만 줄 바꿈이 제거됩니다 (예상대로).

다음 세 줄은 IFS에 공백이 포함되어 있으므로 초기 공백을 제거하고 var 행을 나머지 잔액으로 설정하십시오.

마지막 네 줄은 따옴표없는 변수의 기능을 보여줍니다. 값이 (여러) 공백으로 분할되고 다음과 같이 인쇄됩니다.bar,baz,qux,


4

unset IFS IFS가 "\ t \ n"으로 추정 되더라도 IFS를 지 웁니다.

$ echo "'$IFS'"
'   
'
$ IFS=""
$ echo "'$IFS'"
''
$ unset IFS
$ echo "'$IFS'"
''
$ IFS=$' \t\n'
$ echo "'$IFS'"
'   
'
$

bash 버전 4.2.45 및 3.2.25에서 동일한 동작으로 테스트되었습니다.


질문과에 대해 얘기하지 않는 링크 된 문서 unsetIFS여기 허용 대답의 의견에 설명 된대로.
ILMostro_7 5
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.