“IFS = read -r line”이해


60

내부 필드 구분자 변수에 값을 추가 할 수 있다는 것을 분명히 알고 있습니다. 예를 들면 다음과 같습니다.

$ IFS=blah
$ echo "$IFS"
blah
$ 

또한 read -r line데이터 stdin를 변수 이름으로 저장 한다는 것을 이해합니다 line.

$ read -r line <<< blah
$ echo "$line"
blah
$ 

그러나 어떻게 명령이 변수 값을 할당 할 수 있습니까? 그리고 그것은에서 처음으로 데이터를 저장하지 stdin변수에 line다음의 값 줄 line에를 IFS?


답변:


104

어떤 사람들은 read선을 읽는 명령 인 잘못된 개념을 가지고 있습니다 . 그렇지 않습니다.

read(역 슬래시 연속) 줄에서 단어 를 읽습니다 . 여기서 단어는 $IFS구분되고 역 슬래시 는 구분 기호를 이스케이프하거나 줄을 계속 사용하는 데 사용할 수 있습니다.

일반적인 구문은 다음과 같습니다.

read word1 word2... remaining_words

read이스케이프되지 않은 줄 바꿈 문자 (또는 입력 끝)를 찾을 때까지 한 번에 한 바이트 씩 stdin을 읽고 복잡한 규칙에 따라이를 나누고 그 결과를 $word1, $word2... 로 저장합니다 $remaining_words.

예를 들어 다음과 같은 입력에서 :

  <tab> foo bar\ baz   bl\ah   blah\
whatever whatever

기본값이 인 $IFS경우 다음 read a b c을 지정합니다.

  • $afoo
  • $bbar baz
  • $cblah blahwhatever whatever

이제 하나의 인수 만 전달하면 해당되지 않습니다 read line. 아직 read remaining_words입니다. 백 슬래시 처리는 여전히 수행되며 IFS 공백 문자는 여전히 시작과 끝에서 제거됩니다.

-r옵션은 백 슬래시 처리를 제거합니다. 따라서 위의 동일한 명령 -r이 대신

  • $afoo
  • $bbar\
  • $cbaz bl\ah blah\

분할 부분의 $IFS경우 IFS 공백 문자 (공백 및 탭 (여기서는 -d를 사용하지 않는 한 여기서는 중요하지 않음))의 두 가지 문자 클래스가 있음을 인식하는 것이 중요합니다 . $IFS) 및 기타 의 기본값으로 설정 하십시오. 이 두 클래스의 캐릭터에 대한 처리 방법이 다릅니다.

IFS=:( :아닌 IFS 공백 문자 인), 같은 입력 :foo::bar::으로 분할된다 "", "foo", "", bar""(와 여분의 ""그 제외하고는 중요하지 않습니다하지만 일부 구현과 함께 read -a). 우리가 대체하면되지만 :공간, 분할 만에 완료 foo하고 bar. 그것은 앞뒤의 것들을 무시하고 그것들의 순서는 하나처럼 취급됩니다. 공백 문자와 공백 문자가 아닌 문자를 조합 할 때 추가 규칙이 있습니다 $IFS. 일부 구현은 IFS ( IFS=::또는 IFS=' ') 의 문자를 두 배로하여 특수 처리를 추가 / 제거 할 수 있습니다 .

따라서 여기서 앞뒤 이스케이프 처리되지 않은 공백 문자를 제거하지 않으려면 IFS에서 해당 IFS 공백 문자를 제거해야합니다.

공백이 아닌 IFS 문자의 경우에도 입력 행에 해당 문자 중 하나만 포함 되고 POSIX 쉘 ( 또는 일부 버전이 아님)이 IFS=: read -r word있는 입력과 같은 행의 마지막 문자 인 경우 해당 입력 하나로 간주 하는 쉘에서 문자가 있기 때문에 단어 로 간주됩니다 터미네이터 , 그래서 포함 하지 .foo:zshpdkshfoo$IFSwordfoofoo:

따라서 read내장으로 한 줄의 입력을 읽는 표준 방법 은 다음 과 같습니다.

IFS= read -r line

(대부분의 read구현에서는 NUL 문자가에서 제외하고 지원되지 않기 때문에 텍스트 줄에서만 작동합니다 zsh.)

var=value cmd구문을 사용하면 IFS해당 cmd명령 기간 동안 만 다르게 설정 됩니다.

연혁

read내장은 Bourne 쉘에 의해 도입 읽고 이미 한 단어 가 아니라 라인. 최신 POSIX 셸에는 몇 가지 중요한 차이점이 있습니다.

Bourne 쉘 은 Korn 쉘에 의해 도입 된 옵션을 read지원하지 않으므로 -r, 입력과 같은 것을 사전 처리하는 것 이외의 백 슬래시 처리를 비활성화 할 방법이 없습니다 sed 's/\\/&&/g'.

Bourne 쉘에는 두 개의 클래스 클래스 (ksh에 의해 다시 도입 됨)라는 개념이 없었습니다. 보른에서 IFS의 공백 문자 KSH에서하는 모든 문자가 같은 치료를 받아야 쉘, 즉 IFS=: read a b c같은 입력에 foo::bar할당합니다 bar$b하지 빈 문자열입니다.

Bourne 쉘에서

var=value cmd

cmd내장 (있는 경우 read)이면 완료 후로 var설정된 상태로 유지됩니다 . Bourne 쉘에서는 확장뿐만 아니라 모든 것을 분할하는 데 사용 되기 때문에 특히 중요 합니다. 또한 Bourne 쉘에서 공백 문자를 제거하면 더 이상 작동하지 않습니다.valuecmd$IFS$IFS$IFS"$@"

Bourne 쉘에서, 화합물 명령을 리디렉션 (같은 초기 버전에서도 일을이 서브 쉘에서 실행됩니다 read var < file또는 exec 3< file; read var <&3작동하지 않았다), 그래서 Bourne 쉘이 사용하기에 그것은 드문 read단말기에 사용자 입력을 제외한 아무것도 (그 라인 연속 처리가 의미가있는 곳)

HP / UX와 같은 일부 Unices에는 util-linux여전히 line한 줄의 입력을 읽는 명령이 있습니다 ( 단일 UNIX 사양 버전 2 까지 표준 UNIX 명령으로 사용됨 ).

기본적으로 head -n 1한 줄에 두 바이트 이상 읽지 않도록 한 번에 한 바이트 씩 읽는다 는 점을 제외 하면 동일 합니다. 이러한 시스템에서 다음을 수행 할 수 있습니다.

line=`line`

물론 이것은 새로운 프로세스를 생성하고 명령을 실행하고 파이프를 통해 출력을 읽는 것을 의미하므로 ksh보다 훨씬 덜 효율적 IFS= read -r line이지만 훨씬 더 직관적입니다.


3
+1 bash의 IFS에서 공간 / 탭과 "기타"에 대한 다른 처리에 대한 유용한 통찰력에 감사드립니다. (그리고 bash (및 다른 posix 쉘)와 일반적인 sh차이점에 대한 통찰력은 휴대용 스크립트를 작성하는 데 유용합니다!)
Olivier Dulac

적어도의 경우 bash-4.4.19while read -r; do echo "'$REPLY'"; done작동합니다 while IFS= read -r line; do echo "'$line'"; done.
x-yuri

이것은 "... 읽는 잘못된 개념은 한 줄을 읽는 명령이다 ..."라고 생각 read합니다. 그 비 오용적인 개념은 무엇입니까? 또는 첫 번째 문장은 기술적으로는 정확하지만 실제로 잘못된 개념은 "읽기는 줄에서 단어를 읽는 명령입니다. 너무 강력하기 때문에 다음을 수행하여 파일에서 줄을 읽을 수 있습니다. IFS= read -r line"
마이크 S

8

이론

여기에는 두 가지 개념이 있습니다.

  • IFS입력 필드 구분 기호입니다. 즉, 문자열 읽기는의 문자를 기준으로 분할됩니다 IFS. 명령 행에서 IFS일반적으로 공백 문자가 표시되므로 명령 행이 공백으로 분할됩니다.
  • 같은 VAR=value command것을 하는 것은 "명령의 환경을 수정 VAR하여 가치를 가질 것 value"을 의미합니다. 기본적으로 명령 에는 값이있는 것으로 command표시 되지만 그 이후에 실행 된 명령은 여전히 이전 값을 갖는 것으로 표시 됩니다. 즉, 해당 변수는 해당 명령문에 대해서만 수정됩니다.VARvalueVAR

이 경우

따라서 할 때 빈 문자열로 IFS= read -r line설정 IFS하는 것입니다 (문자를 사용하여 분할하지 않으므로 분할이 발생하지 않음). 그러면 read전체 줄을 읽고 line변수에 할당 될 한 단어로 볼 수 있습니다 . 변경 사항 IFS은 해당 명령문에만 영향을 미치므로 다음 명령은 변경 사항의 영향을받지 않습니다.

부수적으로

명령이 정확하고 설정, 의도 한대로 작동하지만 IFS이 경우 없는 (1) 하지 않을 필요. 내장 섹션 의 bash매뉴얼 페이지에 작성된 바와 같이 read:

한 줄은 표준 입력 [...]에서 읽히고 첫 번째 단어는 첫 번째 이름에, 두 번째 단어는 두 번째 이름에 할당되며, 나머지 단어와 그 중간 분리자는성에 할당됩니다 . 입력 스트림에서 읽은 단어가 이름보다 적은 경우 나머지 이름에는 빈 값이 할당됩니다. 의 문자 IFS는 행을 단어로 나누는 데 사용됩니다. [...]

line변수 만 있기 때문에 모든 단어가 어쨌든 변수에 할당되므로 선행 및 후행 공백 문자 1필요하지 않으면 그냥 쓰고 쓸 read -r line수 있습니다.

[1] unset또는 기본값 $IFSread선행 / 트레일 링 IFS 공백 을 고려하는 방법의 예와 같이 다음을 시도 할 수 있습니다.

echo ' where are my spaces? ' | { 
    unset IFS
    read -r line
    printf %s\\n "$line"
} | sed -n l

그것을 실행하면 IFS설정되지 않은 경우 선행 및 후행 문자가 생존 하지 않음을 알 수 있습니다. 또한 $IFS스크립트 앞부분에서 수정 해야 할 경우 이상한 일이 발생할 수 있습니다.


5

이 문장을 두 부분으로 읽어야합니다. 첫 번째 것은 IFS 변수의 값을 지 웁니다. 즉, 더 읽기 쉬운 것과 같고 IFS=""두 번째는 linestdin 에서 변수를 읽습니다 read -r line.

이 구문에서 구체적으로 설명하는 것은 IFS 영향이 과도하며 read명령 에만 유효하다는 것입니다 .

내가 빠진 것이 아닌 한, 특정 경우 지우기 는 설정되어 IFS있더라도 아무런 영향을 미치지 않습니다 . 변수 IFS에서 전체 줄을 읽습니다 line. 둘 이상의 변수가 read명령에 매개 변수로 전달 된 경우에만 동작이 변경되었을 수 있습니다 .

편집하다:

이는 특수하게 처리되지 않는 -r입력 \, 즉 백 슬래시가 line변수에 포함되고 여러 줄 입력을 허용하는 연속 문자가 아닌 입력을 허용합니다.

$ read line; echo "[$line]"   
abc\
> def
[abcdef]
$ read -r line; echo "[$line]"  
abc\
[abc\]

IFS를 지우면 읽기와 후행 공백 또는 탭 문자를 트리밍하여 읽기를 방지하는 부작용이 있습니다.

$ echo "   a b c   " | { IFS= read -r line; echo "[$line]" ; }   
[   a b c   ]
$ echo "   a b c   " | { read -r line; echo "[$line]" ; }     
[a b c]

그 차이를 지적 해준 rici에게 감사합니다.


누락 된 것은 IFS가 변경되지 않으면 read -r line입력에 line변수 를 할당하기 전에 선행 및 후행 공백을 자릅니다 .
rici

@rici 나는 그런 것을 의심했지만 단어 사이에서 IFS 문자 만 검사했지만 앞뒤로 움직이지 않았습니다. 그 사실을 지적 해 주셔서 감사합니다!
jlliagre

IFS를 지우면 여러 변수가 할당되지 않습니다 (부작용). IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"표시됩니다-aa bb--
kyodev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.