$ IFS 변수를 "백업"하는 것이 현명한 접근입니까?


19

나는 항상 $IFS세계를 망칠 수 있기 때문에 엉망이되는 것을 주저합니다 .

그러나 종종 문자열을 bash 배열에로드하는 것이 멋지고 간결하며 bash 스크립팅의 경우 간결함을 얻기가 어렵습니다.

따라서 시작 $IFS변수를 다른 변수 에 "저장"하고 $IFS무언가를 사용한 후에 즉시 복원 하려고하면 아무것도 아닌 것보다 낫습니다 .

이것이 실용적입니까? 아니면 본질적으로 무의미하며 IFS후속 사용에 필요한 것으로 직접 설정해야 합니까?


왜 실용적이지 않습니까?
Bratchley

IFS를 설정 해제하면 작업이 제대로 수행됩니다.
llua

1
IFS 설정을 해제하면 정상적으로 작동한다고 말하는 사람들에게는 상황에 따라 다릅니다 . stackoverflow.com/questions/39545837/… 필자의 경험에 따르면 $' \t\n'bash를 사용하는 경우 IFS를 쉘 인터프리터의 기본값으로 수동으로 설정하는 것이 가장 좋습니다 . unset $IFS항상 기본값으로 기대되는 값으로 복원하지는 않습니다.
Darrel Holt

답변:


9

필요에 따라 IFS를 저장하고 할당 할 수 있습니다. 그렇게하는 데 아무런 문제가 없습니다. 배열 할당 예와 같이 일시적이고 신속하게 수정 한 후 복원 할 값을 저장하는 것은 드문 일이 아닙니다.

@llua가 귀하의 질문에 대한 언급에서 언급했듯이 IFS를 설정 해제하면 스페이스 탭 줄 바꿈을 지정하는 것과 동일한 기본 동작이 복원됩니다.

IFS를 명시 적으로 설정 / 설정 해제 하지 않는 것이 더 문제가 될 수있는 방법을 고려해 볼 가치가 있습니다 .

POSIX 2013 버전 2.5.3 쉘 변수에서 :

쉘을 호출 할 때 환경에서 IFS의 값 또는 환경에서 IFS가없는 경우 구현시 쉘이 IFS를 <space> <tab> <newline>으로 설정해야합니다. .

POSIX 호환 호출 쉘은 해당 환경에서 IFS를 상속하거나 상속하지 않을 수 있습니다. 이것부터 :

  • 이식 가능한 스크립트는 환경을 통해 IFS를 확실하게 상속 할 수 없습니다.
  • "$*"환경에서 IFS를 초기화하는 셸에서 실행될 수 있지만 기본 분할 동작 (또는 경우 조인) 만 사용하려는 스크립트는 환경 침입으로부터 자체적으로 방어하기 위해 IFS를 명시 적으로 설정 / 설정 해제해야합니다.

NB이 토론에서 "부르다"라는 단어에는 특별한 의미가 있음을 이해하는 것이 중요합니다. 쉘은 이름 ( #!/path/to/shellshebang 포함)을 사용하여 명시 적으로 호출 된 경우에만 호출됩니다 . $(...)또는에 의해 생성 될 수있는 서브 쉘 cmd1 || cmd2 &은 호출 된 쉘이 아니며 IFS (대부분의 실행 환경과 함께)는 부모의 쉘과 동일합니다. 호출 된 쉘은 값을 $pid로 설정하고 서브 쉘은이를 상속합니다.


이것은 단지 단순한 청각이 아니다. 이 영역에는 실제로 차이가 있습니다. 다음은 몇 가지 다른 셸을 사용하여 시나리오를 테스트하는 간단한 스크립트입니다. 수정 된 IFS (로 설정 됨 :)를 호출 된 쉘로 내 보낸 다음 기본 IFS를 인쇄합니다.

$ cat export-IFS.sh
export IFS=:
for sh in bash ksh93 mksh dash busybox:sh; do
    printf '\n%s\n' "$sh"
    $sh -c 'printf %s "$IFS"' | hexdump -C
done

IFS는 일반적으로 내보내기로 표시되지 않지만, bash, ksh93 및 mksh가 환경을 무시하는 방법에 주목 IFS=:하고 dash 및 busybox는이를 존중합니다.

$ sh export-IFS.sh

bash
00000000  20 09 0a                                          | ..|
00000003

ksh93
00000000  20 09 0a                                          | ..|
00000003

mksh
00000000  20 09 0a                                          | ..|
00000003

dash
00000000  3a                                                |:|
00000001

busybox:sh
00000000  3a                                                |:|
00000001

일부 버전 정보 :

bash: GNU bash, version 4.3.11(1)-release
ksh93: sh (AT&T Research) 93u+ 2012-08-01
mksh: KSH_VERSION='@(#)MIRBSD KSH R46 2013/05/02'
dash: 0.5.7
busybox: BusyBox v1.21.1

bash, ksh93 및 mksh가 환경에서 IFS를 초기화하지 않더라도 수정 된 IFS를 다시 내 보냅니다.

어떤 이유로 든 환경을 통해 IFS를 이식 가능하게 전달해야하는 경우 IFS 자체를 사용하여 수행 할 수 없습니다. 값을 다른 변수에 지정하고 해당 변수를 내보내도록 표시해야합니다. 그런 다음 어린이는 해당 값을 IFS에 명시 적으로 할당해야합니다.


따라서, 내가 말하면, 대부분의 상황에서 값 을 명시 적으로 지정하는 것이 이식성이 IFS좋기 때문에 원래 값을 "보존"하려는 시도조차 끔찍하게 생산적이지 않은 경우가 많습니다.
Steven Lu

1
가장 중요한 문제는 스크립트가 IFS를 사용하는 경우 IFS를 명시 적으로 설정 / 설정 해제하여 원하는 값이되도록해야합니다. 일반적으로 인용되지 않은 매개 변수 확장, 인용되지 않은 명령 대체, 인용되지 않은 산술 확장, reads 또는 이중 인용 인용 이있는 경우 스크립트의 동작은 IFS에 따라 다릅니다 $*. 이 목록은 내 머리 꼭대기에 있으므로 포괄적이지 않을 수 있습니다 (특히 현대 쉘의 POSIX 확장을 고려할 때).
Barefoot IO

10

일반적으로 조건을 기본값으로 되 돌리는 것이 좋습니다.

그러나이 경우에는별로 없습니다.

왜?:

또한 IFS 값을 저장하는 데 문제가 있습니다.
원래 IFS가 설정 해제 된 경우 코드 IFS="$OldIFS"는 IFS를 설정 ""하지 않고 설정합니다.

실제로 설정되지 않은 경우에도 IFS의 값을 유지하려면 다음을 사용하십시오.

${IFS+"false"} && unset oldifs || oldifs="$IFS"    # correctly store IFS.

IFS="error"                 ### change and use IFS as needed.

${oldifs+"false"} && unset IFS || IFS="$oldifs"    # restore IFS.

IFS는 실제로 설정을 해제 할 수 없습니다. 설정을 해제하면, 쉘은이를 기본값으로 되돌립니다. 따라서 저장할 때 실제로 확인할 필요가 없습니다.
filbranden

주의 점에서 bash, unset IFS그것은 부모 컨텍스트 (기능 컨텍스트)의 지역이 아니라 현재 컨텍스트에 선언 된 경우 해제 IFS에 실패합니다.
Stéphane Chazelas

5

글로벌 클로버 링에 대해 주저 할 권리가 있습니다. 실제 전역을 수정 IFS하거나 번거롭고 오류가 발생하기 쉬운 저장 / 복원 댄스를 수행 하지 않고도 깨끗한 작업 코드를 작성할 수 있습니다 .

당신은 할 수 있습니다 :

  • 단일 호출에 대해 IFS를 설정하십시오.

    IFS=value command_or_function

    또는

  • 서브 쉘 내부에 IFS를 설정하십시오.

    (IFS=value; statement)
    $(IFS=value; statement)

  • 배열에서 쉼표로 구분 된 문자열을 얻으려면

    str="$(IFS=, ; echo "${array[*]-}")"

    참고 : -설정 해제시 기본값set -u 을 제공 하여 (이 경우 빈 문자열 인 경우) 빈 배열을 보호하기위한 것 입니다.

    IFS수정에 의해 산란 서브 쉘 내부에만 적용 $() 명령 치환 . 서브 쉘은 호출하는 쉘 변수의 사본을 가지고 있으므로 해당 값을 읽을 수 있기 때문에 서브 쉘에 의해 수행 된 모든 수정 사항은 서브 쉘의 사본에만 영향을 미치며 상위 변수에는 영향을 미치지 않습니다.

    당신은 또한 생각할 수 있습니다 : 왜 서브 쉘을 건너 뛰고 이것을하지 마십시오 :

    IFS=, str="${array[*]-}"  # Don't do this!

    여기에는 명령 호출이 없으며이 줄은 다음과 같이 두 개의 독립적 인 후속 변수 할당으로 해석됩니다.

    IFS=,                     # Oops, global IFS was modified
    str="${array[*]-}"

    마지막으로이 변형이 작동하지 않는 이유를 설명하겠습니다.

    # Notice missing ';' before echo
    str="$(IFS=, echo "${array[*]-}")" # Don't do this! 

    echo명령은 참으로 그와 함께 호출됩니다 IFS에 변수를 설정 ,하지만, echo신경 또는 사용하지 않습니다 IFS. "${array[*]}"문자열 로 확장 하는 마술은 echo호출 되기 전에 (서브) 쉘 자체에 의해 수행 됩니다.

  • 전체 파일 ( NULL바이트를 포함하지 않는 )을 다음과 같은 단일 변수로 읽습니다 VAR.

    IFS= read -r -d '' VAR < "${filepath}"

    주 : IFS=동일하다 IFS=""IFS=''매우 다르다 빈 문자열로 IFS 모두 집합, unset IFS경우 : IFS설정되어 있지 않은, 내부적으로 사용하여 모든 bash는 기능의 동작을하는 IFS것처럼 정확히 동일 IFS의 기본값을했다 $' \t\n'.

    IFS빈 문자열로 설정 하면 선행 및 후행 공백이 유지됩니다.

    -d ''이상이 -d ""만에 현재의 호출을 중지 읽을 알려줍니다 NULL대신 평소 개행의 바이트.

  • 구분자를 $PATH따라 나누려면 ::

    IFS=":" read -r -d '' -a paths <<< "$PATH"

    이 예는 순전히 예시입니다. 구분 기호를 따라 분할하는 일반적인 경우 개별 필드에 해당 구분 기호가 포함되어있을 수 있습니다. .csv열 자체에 쉼표가 포함되어 있는 파일 (일부 방식으로 이스케이프 또는 인용) 이있는 파일 행을 읽으려고합니다 . 위의 스 니펫은 이러한 경우에 의도 한대로 작동하지 않습니다.

    즉,에 :포함 경로 가 발생할 가능성이 적습니다 $PATH. UNIX / Linux 경로 이름에는을 포함 :할 수 $PATH있지만 이스케이프 / 따옴표로 묶인 콜론을 구문 분석하는 코드가 없기 때문에 bash가 경로를 추가하고 실행 파일을 저장 하려고하면 어쨌든 bash가 그러한 경로를 처리 할 수없는 것 같습니다 : bash 4.4의 소스 코드 .

    마지막으로, 스 니펫은 결과 배열의 마지막 요소에 후행 줄 바꿈을 추가하고 (지금 삭제 된 주석에서 @ StéphaneChazelas에 의해 호출 됨) 입력이 빈 문자열 인 경우 출력은 단일 요소입니다. 배열에서 요소는 개행 ( $'\n') 으로 구성됩니다 .

자극

old_IFS="${IFS}"; command; IFS="${old_IFS}"전 세계 IFS를 다루는 기본 접근 방식 은 가장 간단한 스크립트에 대해 예상대로 작동합니다. 그러나 복잡성을 추가하자마자 쉽게 분리되어 미묘한 문제가 발생할 수 있습니다.

  • 경우 command도 글로벌 수정하는 bash는 기능입니다 IFS(직접 또는, 그것은 호출하는 내부의 또 다른 기능보기에서 숨겨진), 그래서 실수하는 동안 것은 같은 글로벌 사용하여 old_IFS복원 / 저장을 할 변수를, 당신은 버그를 얻을.
  • @Gilles 의이 의견에서 지적했듯이 원래 상태 IFS가 설정되어 있지 않으면 순진한 저장 후 복원이 작동하지 않으며 일반적으로 사용되는 set -u(일명 set -o nounset) 쉘 옵션을 사용하면 완전히 실패 할 수 있습니다 시행 중입니다.
  • 일부 셸 코드는 신호 처리기와 같은 기본 실행 흐름에 대해 비동기 적으로 실행될 수 있습니다 (참조 help trap). 해당 코드가 전역을 수정 IFS하거나 특정 값이 있다고 가정하면 미묘한 버그가 발생할 수 있습니다.

보다 강력한 저장 / 복원 순서를 고안 할 수 있습니다 (예 : 이 문제의 일부 또는 전부를 피하기 위해이 다른 답변 에서 제안 된 것과 같음) IFS. 코드 가독성 및 유지 보수성이 감소합니다.

라이브러리와 유사한 스크립트에 대한 추가 고려 사항

IFS특히 IFS호출자에 의해 부과 된 전역 상태 ( , 셸 옵션 등)에 관계없이 코드가 강력하게 작동해야하고 셸 상태를 전혀 방해하지 않는 쉘 함수 라이브러리 작성자에게 특히 중요합니다 . 항상 정적 상태로 유지하십시오.

라이브러리 코드를 작성할 때 IFS특정 값 (기본값은 아님)을 가지거나 전혀 설정하지 않아도됩니다. 대신 IFS동작이에 따라 다른 스 니펫에 대해 명시 적으로 설정해야합니다 IFS.

IFS이 답변에 설명 된 두 가지 메커니즘 중 어느 것이 효과를 현지화하는 데 적합한지를 사용하여 값이 중요한 모든 코드 줄에서 명시 적으로 필요한 값으로 설정 되면 (기본 값이더라도) 글로벌 상태와 무관하며 전복 상태를 피할 수 있습니다. 이 접근 방식은 IFS텍스트 비용을 최소화하면서도 가장 기본적인 저장 / 복원에 비해이 명령 / 확장에 중요한 스크립트를 읽는 사람에게 매우 명백한 이점을 제공합니다 .

IFS어쨌든 어떤 코드가 영향을 받 습니까?

다행히도 IFS중요한 시나리오는 많지 않습니다 ( 항상 확장을 인용 한다고 가정 ).

  • "$*""${array[*]}"확장
  • read내장 된 여러 변수 ( read VAR1 VAR2 VAR3) 또는 배열 변수 ( read -a ARRAY_VAR_NAME)의 호출
  • 의 호출 read은 / 선도에 나타나는 공백 또는 공백이 아닌 문자를 후행에 올 때 하나의 변수를 대상으로 IFS.
  • 단어 분리 (예 : 인용되지 않은 확장의 경우, 전염병처럼 피하고 싶을 수도 있음 )
  • 덜 일반적인 다른 시나리오 ( IFS @ Greg 's Wiki 참조 )

나는 어떤 구성 요소도 : 자체 문장을 포함하지 않는다고 가정하면 : 구분 기호를 따라 $ PATH를 나누는 것을 이해할 수 없다 . 구분 기호는 :언제 구성 요소에 포함될 수 :있습니까?
Stéphane Chazelas

@ StéphaneChazelas 음, :대부분의 UNIX / Linux 파일 시스템에서 파일 이름으로 사용할 수있는 유효한 문자이므로 이름이 포함 된 디렉토리를 가질 수 있습니다 :. 아마도 일부 포탄 탈출 조항이 :같은 것을 사용하여 PATH에를 \:한 다음 열을 실제 구분이되지 않습니다 나타나는 볼 것이다 (배쉬는 이스케이프를 허용하지 않는 것 같다. 낮은 수준의 기능을 사용할 때 반복하는이 끝난 $PATH단지 검색을위한 :의 C 문자열 : git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).
sls

분할 $PATH예를 :더 명확 하게하기 위해 답을 수정했습니다 .
sls

1
SO에 오신 것을 환영합니다! 깊이있는 답변에 감사드립니다 :)
Steven Lu

1

이것이 실용적입니까? 아니면 본질적으로 무의미하며 IFS를 후속 용도에 필요한 것으로 직접 설정해야합니까?

IFS를 설정해야 $' \t\n'할 때 오타가 발생할 위험이있는 이유는 무엇입니까?

OIFS=$IFS
do_your_thing
IFS=$OIFS

또는 다음과 같이 설정 / 수정 된 변수가 필요하지 않은 경우 서브 쉘을 호출 할 수 있습니다.

( IFS=:; do_your_thing; )

IFS처음 설정 하지 않으면 작동하지 않기 때문에 위험합니다 .
Gilles 'SO- 악의를 멈춰라'
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.