$IFS 변수를 "백업"하는 합리적인 방법입니까?

$IFS 변수를 "백업"하는 합리적인 방법입니까?

$IFS나는 큰 그림을 망치기 때문에 항상 혼란을 겪는 것을 주저합니다 .

그러나 일반적으로 bash 배열에 문자열을 로드하는 것은 훌륭하고 간결하지만, bash 스크립트에서는 간결성을 달성하기 어렵습니다.

$IFS그래서 나는 시작 내용을 다른 변수에 "저장"한 다음 $IFS무언가 사용을 마치자마자 복원하는 것이 아무것도 하지 않는 것보다 나을 것이라고 생각했습니다 .

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

답변1

일반적으로 조건을 기본값으로 복원하는 것이 좋습니다.

그러나 이 경우에는 그렇지 않습니다.

왜? :

또한 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.

답변2

필요에 따라 IFS에 저장하고 할당할 수 있습니다. 이렇게 하는 데에는 아무런 문제가 없습니다. 배열 할당 예제에서처럼 임시적이고 빠른 수정 후 복원을 위해 해당 값을 저장하는 것은 드문 일이 아닙니다.

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

추가 문제가 어떻게 발생할 수 있는지 고려해 볼 가치가 있습니다.아니요IFS를 명시적으로 설정/설정 해제하는 것이 이 작업을 수행하는 것보다 더 중요합니다.

POSIX 버전 2013부터,2.5.3 쉘 변수:

구현은 쉘을 호출할 때 환경의 IFS 값을 무시할 수 있거나 IFS가 환경에 존재하지 않을 수 있습니다. 이 경우 쉘은 호출할 때 IFS를 <space> <tab> <newline>으로 설정해야 합니다.

POSIX 호환 호출 셸은 해당 환경에서 IFS를 상속할 수도 있고 상속하지 않을 수도 있습니다. 다음에서 알 수 있습니다.

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

참고: 이 논의에서 "호출"이라는 단어에는 특정한 의미가 있다는 점을 이해하는 것이 중요합니다. #!/path/to/shell쉘은 해당 이름( shebang 포함)을 사용하여 명시적으로 호출되는 경우에만 호출됩니다. 하위 쉘( $(...)또는 에 의해 생성될 수 있는 하위 쉘 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에 해당 값을 명시적으로 할당해야 합니다.

답변3

큰 그림을 망치는 것을 주저하는 것이 옳습니다. 걱정하지 마세요. 실제 전역을 수정 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! 
    

    명령 은 변수 set 으로 echo호출되지만 신경 쓰지 않거나 사용하지 않습니다 . 문자열로 확장하는 마법은 호출 전에 (하위)쉘 자체에 의해 수행됩니다.IFS,echoIFS"${array[*]}"echo

  • 전체 파일( NULL바이트 제외)을 단일 변수로 읽으려면 다음을 수행하십시오 VAR.

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

    참고: IFS=IFS를 빈 문자열로 설정하는 및 와 마찬가지로 이는 다음과 매우 다릅니다. 설정하지 않으면 내부적으로 사용되는 모든 bash 함수는 기본값과 똑같이 동작합니다.IFS=""IFS=''unset IFSIFSIFSIFS$' \t\n'

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

    또는 일반적인 개행 대신 1바이트에서만 현재 호출을 중지하도록 -d ''read에 지시합니다 .-d ""NULL

  • $PATH구분 기호에 따라 분할 ::

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

    이 예는 단지 설명을 위한 것입니다. 구분 기호를 따라 분할하는 일반적인 경우 개별 필드에는 해당 구분 기호(이스케이프 버전)가 포함될 수 있습니다. .csv열 자체에 쉼표(어떤 방식으로든 이스케이프되거나 인용됨)가 포함될 수 있는 파일의 한 줄을 읽으려고 한다고 상상해 보십시오 . 이 경우 위의 코드 조각은 예상대로 작동하지 않습니다.

    즉, UNIX/Linux 경로 이름은 포함을 허용하지만 해당 :경로 를 경로에 추가하고 거기에 실행 파일을 저장하려고 하면 bash는 어쨌든 그러한 경로를 처리할 수 없는 것 같습니다 . 코드는 이스케이프/인용된 콜론을 구문 분석할 수 있습니다.$PATH:$PATHBash 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는 코드의 가독성과 유지 관리성을 감소시킵니다.

라이브러리 스크립트에 대한 기타 고려 사항

IFSIFS이는 호출자가 부과한 전역 상태( , 셸 옵션 등)에 관계없이 코드가 강력하게 작동하고 해당 상태를 전혀 방해하지 않는지 확인해야 하는 셸 라이브러리 작성자에게 특히 중요합니다 (호출자는 이에 의존할 수 있음). 항상 정적인 상태로 유지하려면).

라이브러리 코드를 작성할 때 IFS특정 값(기본값도 아님)에 의존하거나 전혀 설정되지 않을 수도 있습니다. 대신 명시적 IFS으로 IFS.

IFS현지화 효과에 대해 이 답변에 설명된 두 메커니즘 중 하나를 사용하여 해당 값이 중요한 모든 코드 줄에서 필요한 값(기본값인 경우에도)으로 명시적으로 설정된 경우 코드는 둘 다 독립적입니다. 글로벌 상태를 완전히 파괴하는 것을 방지합니다. 이 접근 방식은 (가장 기본적인 저장/복원에 비해) 최소한의 텍스트 비용으로 수행할 수 있다는 추가 이점을 제공하므로 IFS스크립트를 읽는 사람들에게 매우 명확하며 이는 이 명령/확장에 매우 중요합니다.

정확히 어떤 코드가 영향을 받나요 IFS?

IFS다행히 중요한 장면은 그리 많지 않습니다 .항상 확장명을 인용하세요):

  • "$*""${array[*]}"확장
  • read내장 대상 다중 변수( read VAR1 VAR2 VAR3) 또는 배열 변수( read -a ARRAY_VAR_NAME) 를 호출합니다 .
  • read선행/후행 공백 또는 공백이 아닌 문자가 있는 경우 단일 변수에 대한 IFS호출입니다 .
  • 단어 분사(예: 인용되지 않은 확장,전염병처럼 피하고 싶을지도 몰라)
  • 기타 덜 일반적인 상황(참조:IFS @그레그 위키)

답변4

이것이 실용적인가요? 아니면 본질적으로 의미가 없으며 이후 사용에 필요한 값으로 IFS를 다시 설정해야 합니까?

$' \t\n'당신이 해야 할 일은 그것뿐일 때

OIFS=$IFS
do_your_thing
IFS=$OIFS

또는 변수를 설정/수정할 필요가 없는 경우 서브셸을 호출할 수 있습니다.

( IFS=:; do_your_thing; )

관련 정보