"IFS=read-r-line" 이해

"IFS=read-r-line" 이해

내부 필드 구분 변수에 값을 추가하는 것이 가능하다는 것을 분명히 알고 있습니다. 예를 들어:

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

read -r line또한 이것이 데이터를 stdin다음과 같은 변수 에 저장한다는 것도 배웠습니다 line.

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

그러나 명령은 어떻게 변수에 값을 할당합니까? 먼저 stdinto 변수의 데이터를 저장 line한 다음 lineto 의 값을 제공합니까 IFS?

답변1

POSIX 쉘에서는 read옵션이 없으면 읽을 수 없습니다.철사, 그것은 읽습니다성격단어가 구분된(백슬래시로 계속되는) 행에서 $IFS백슬래시를 사용하여 구분 기호(또는 연속 행)를 이스케이프할 수 있습니다.

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

read word1 word2... remaining_words

read이스케이프되지 않은 개행 문자(또는 입력 끝)가 발견될 때까지 한 번 에 1바이트씩 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 lineStill 이 되지 않습니다 read remaining_words. 백슬래시 처리는 계속 수행되며 IFS 공백 문자²는 여전히 처음과 끝에서 제거됩니다.

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

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

이제 분할 부분의 경우 두 가지 범주의 문자가 있다는 것을 인식하는 것이 중요합니다 $IFS. $IFS) 및 기타 기본값 으로 발생합니다 . 이 두 가지 유형의 역할은 다르게 취급됩니다.

IFS=:( :IFS가 아닌) 공백 문자 의 경우 이와 같은 입력은 , 및 (및 일부 구현 추가 항목 , 그 이상은 중요하지 않음) :foo::bar::로 분할됩니다 . 그리고 공백으로 바꾸면 합계로만 분할됩니다. 즉, 선행 및 후행 시퀀스는 무시되고 해당 시퀀스는 하나의 시퀀스로 처리됩니다. 에서 공백과 공백이 아닌 문자를 결합할 때 추가 규칙이 있습니다. 일부 구현에서는 IFS의 문자(또는)를 두 배로 늘려 특수 처리를 추가/제거할 수 있습니다."""foo"""bar""""read -a:foobar$IFSIFS=::IFS=' '

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

공백이 아닌 IFS 문자가 있는 경우에도 입력 줄에 이러한 문자 중 하나만 포함되어 있고 IFS=: read -r wordPOSIX 셸(일부 버전이 아님)에 대한 줄의 마지막 문자(비슷한 입력에서와 같이) 인 경우 입력은 단어로 처리됩니다. 왜냐하면 이러한 쉘에서 문자는 다음과 같이 처리되기 때문입니다.foo:zshpdkshfoo$IFS터미네이터이므로 은 word포함되고 foo포함되지 않습니다 foo:.

따라서 내장 함수를 사용하여 입력 줄을 읽는 표준 방법은 다음과 read같습니다.

IFS= read -r line

(대부분의 read구현에서는 NUL 문자가 지원되지 않으므로 이는 텍스트 줄에만 작동합니다 zsh.)

명령이 실행되는 동안에만 다른 설정이 이루어지도록 var=value cmd하려면 구문을 사용하십시오 .IFScmd

역사적 기록

read내장 함수는 Bourne 쉘에 의해 도입되었으며 이미 읽을 수 있습니다.성격, 줄이 아닙니다. 최신 POSIX 셸에는 몇 가지 중요한 차이점이 있습니다.

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

Bourne 쉘에는 두 가지 유형의 문자 개념이 없습니다(이는 ksh에서도 도입되었습니다). Bourne 셸에서 모든 문자는 빈 문자열이 아닌 ksh, 즉 IFS=: read a b c입력( foo::bar에 할당된 경우 bar) 의 IFS 공백 문자와 동일하게 처리됩니다 .$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터미널의 사용자 입력 이외의 용도로 거의 사용되지 않습니다. (라인 연속 처리가 의미가 있는 경우)

일부 Unices(예: HP/UX, 에도 있음 util-linux)에는 여전히 line입력 줄을 읽는 명령이 있습니다(이것은 이전까지는 표준 UNIX 명령이었습니다).단일 UNIX 사양 버전 2).

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

line=`line`

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


1 검색 가능한 입력이지만 일부 구현에서는 블록 읽기로 되돌아간 다음 최적화로 역추적할 수 있습니다. ksh93은 한 단계 더 나아가 읽은 내용을 기억하고 다음 read호출 에 사용합니다.현재 손상됨

²IFS 공백 문자, 각 POSIX는 로케일로 분류된 문자 이며 ksh88(POSIX 사양의 기반) 및 대부분의 셸에서 [:space:]발생하는 것처럼 이는 여전히 SPC, TAB 및 NL로 제한됩니다. $IFS이와 관련하여 제가 찾은 유일한 POSIX 호환 셸은 입니다 yash. ksh93( bash5.0 기준) 다른 공백(예: CR, FF, VT...)도 포함하지만 단일 바이트 공백으로 제한됩니다(예: Solaris)(일부 영역에는 단일 바이트의 잘림 방지 공백이 포함됨)

답변2

이론

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

  • IFS는 입력 필드 구분 기호입니다. 이는 읽은 문자열이 의 문자를 기준으로 분할됨을 의미합니다 IFS. 명령줄에서는 IFS일반적으로 공백 문자이므로 명령줄이 공백으로 분할됩니다.
  • 이와 같은 작업을 수행한다는 것은 " 값을 갖도록 VAR=value command명령 환경을 수정 "하는 것을 의미합니다. 기본적으로 명령은 값을 갖는 것으로 간주되지만 그 이후에 실행되는 모든 명령은 여전히 ​​이전 값을 갖는 것으로 간주됩니다 . 즉, 해당 명령문에 대해서만 변수가 수정됩니다.VARvaluecommandVARvalueVAR

이 경우

따라서 를 실행할 때 IFS= read -r line수행할 작업은 IFS빈 문자열(분할에 문자가 사용되지 않으므로 분할이 발생하지 않음)을 설정하여 read전체 줄을 읽고 변수 word 에 할당할 줄로 처리하는 것뿐입니다 line. 변경 사항은 IFS해당 명령문에만 영향을 미치므로 후속 명령은 변경 사항의 영향을 받지 않습니다.

참고로

명령은 정확하고 예상대로 작동하지만 IFS이 경우 설정은 올바르지 않습니다. 어쩌면 1은 아닐지도 몰라필요한. 내장 섹션 bash에 대한 매뉴얼 페이지에 쓰여진 대로 read:

표준 입력 [...]에서 한 줄을 읽으면 첫 번째 단어가 첫 번째 이름에 할당되고 두 번째 단어가 두 번째 이름에 할당되는 식입니다.나머지 단어와 중간 구분 기호를성에 할당. 입력 스트림에서 이름보다 적은 단어를 읽는 경우 나머지 이름에는 null 값이 할당됩니다. 의 문자는 IFS행을 단어로 분할하는 데 사용됩니다. [...]

변수 만 있으므로 line각 단어가 변수에 할당됩니다.앞뒤 공백 문자가 필요하지 않은 경우1 그냥 쓰고 read -r line마무리하시면 됩니다.

unset[1] 기본값 또는 $IFS기본값이 read선행/후행 고려 사항 으로 이어지는 방법의 예IFS 공백,당신은 시도 할 수 있습니다:

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

IFS실행해 보면 설정을 해제하지 않으면 앞뒤 문자가 유지되지 않는다는 것을 알 수 있습니다. 또한 $IFS스크립트 앞부분을 수정하면 이상한 일이 발생할 수 있습니다.

답변3

이 명령문은 두 부분으로 나누어 읽어야 합니다. 첫 번째 부분은 IFS 변수의 값을 지웁니다. 이는 더 읽기 쉬운 것과 동일하며 IFS="", 두 번째 부분은 linestdin에서 변수를 읽습니다 read -r line.

이 구문의 특징은 IFS 효과가 일시적이며 명령에만 영향을 미친다는 것입니다 read.

뭔가 빠진 것이 없다면, 이 특별한 경우에는 무엇을 설정했는지에 관계없이 변수에서 전체 줄을 읽 IFS더라도 지우기가 효과가 없습니다 . 여러 변수가 지시문에 인수로 전달되는 경우에만 동작이 변경됩니다 .IFSlineread

편집하다:

-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에게 감사드립니다.

답변4

이것은좋은 대답쉘 구문 관점에서 보면:

간단한 명령은선택적 변수 할당 순서그다음 공간 분리단어 및 리디렉션, 제어 연산자에 의해 종료됩니다. 첫 번째 단어는 실행할 명령을 지정하고 인수 0으로 전달됩니다. 나머지 단어는 호출된 명령에 인수로 전달됩니다.

~에서배시 참조 3.7.4:

간단한 명령이나 기능의 환경은 다음을 통해 일시적으로 향상될 수 있습니다.앞에 매개변수 할당을 추가하세요., 셸 매개변수에 설명된 대로.이러한 할당 문은 명령으로 표시되는 환경에만 영향을 미칩니다..

IFS=""여기 명령을 통해서만 표시됩니다 read. 즉, 이 행 외에는 IFS어떤 값도 변경되지 않습니다 .IFS

관련 정보