단일 명령문에 대한 IFS 설정


42

단일 명령 / 내장 범위에 대해 사용자 지정 IFS 값을 설정할 수 있다는 것을 알고 있습니다. 단일 명령문에 대한 사용자 정의 IFS 값을 설정하는 방법이 있습니까 ?? 아래를 기준으로 전역 IFS 값이 시도 될 때 영향을 받기 때문에 분명히 그렇지 않습니다.

#check environment IFS value, it is space-tab-newline
printf "%s" "$IFS" | od -bc
0000000 040 011 012
             \t  \n
0000003
#invoke built-in with custom IFS
IFS=$'\n' read -r -d '' -a arr <<< "$str"
#environment IFS value remains unchanged as seen below
printf "%s" "$IFS" | od -bc
0000000 040 011 012
             \t  \n
0000003

#now attempt to set IFS for a single statement
IFS=$'\n' a=($str)
#BUT environment IFS value is overwritten as seen below
printf "%s" "$IFS" | od -bc
0000000 012
         \n
     0000001

답변:


39

일부 포탄 (포함 bash) :

IFS=: command eval 'p=($PATH)'

를 사용 하면 sh / POSIX 에뮬레이션에서 if를 bash생략 할 수 있습니다 command. 그러나 인용되지 않은 변수를 사용할 때는 일반적으로을 수행해야 set -f하며 대부분의 셸에는 지역 범위가 없습니다.

zsh를 사용하면 다음을 수행 할 수 있습니다.

(){ local IFS=:; p=($=PATH); }

$=PATH은 기본적으로 수행되지 않는 단어 분리를 강제로 수행하는 것입니다 zsh(변수 확장시 글 로빙도 수행되지 않으므로 set -fsh 에뮬레이션 이 아닌 한 필요하지 않습니다 ).

(){...}또는 익명 함수function {...} 라고 하며 일반적으로 로컬 범위를 설정하는 데 사용됩니다. 함수에서 로컬 범위를 지원하는 다른 쉘을 사용하면 다음과 비슷한 작업을 수행 할 수 있습니다.

e() { eval "$@"; }
e 'local IFS=:; p=($PATH)'

POSIX 쉘에서 변수 및 옵션에 대한 로컬 범위를 구현하기 위해 https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh에 제공된 기능을 사용할 수도 있습니다 . 그런 다음 다음과 같이 사용할 수 있습니다.

. /path/to/locvar.sh
var=3,2,2
call eval 'locvar IFS; locopt -f; IFS=,; set -- $var; a=$1 b=$2 c=$3'

( 다른 쉘 $PATH에서 zsh와 달리 IFS는 필드 구분자가 아니라 필드 구분 기호입니다.)

IFS=$'\n' a=($str)

같은 두 개의 과제 a=1 b=2입니다.

에 대한 설명 var=value cmd:

에서:

var=value cmd arg

셸은 /path/to/cmd새 프로세스에서 실행 되어 cmdand argin argv[]var=valuein으로 전달 됩니다 envp[]. 그것은 실제로 변수 할당이 아니라 실행 된 명령에 환경 변수를 전달하는 것 입니다. Bourne 또는 Korn 쉘에서을 사용하여 set -k작성할 수도 cmd var=value arg있습니다.

이제는 실행 되지 않는 내장 함수 나 함수에는 적용되지 않습니다 . Bourne 쉘에서 in var=value some-builtin은 ( var는) 나중에 var=value혼자 와 마찬가지로 설정 됩니다. 예를 들어 var=value echo foo(유용하지 않은) 의 동작 echo은 내장 여부에 따라 달라집니다 .

POSIX 및 / 또는 kshBourne 동작이 special builtins 라는 내장 범주에 대해서만 발생한다는 점에서 변경되었습니다 . eval특별한 내장, read그렇지 않습니다. 비 특수 내장의 경우, 내장 명령 의 실행에 대해서만 var=value builtin설정 var하여 외부 명령이 실행될 때와 유사하게 작동합니다.

command명령을 사용하여 해당 특수 내장특수 속성 을 제거 할 수 있습니다 . POSIX가 간과 한 것은 and 내장의 경우 쉘이 변수 스택을 구현해야한다는 것을 의미합니다 ( 또는 범위 제한 명령을 지정하지 않더라도 ).eval.localtypeset

a=0; a=1 command eval 'a=2 command eval echo \$a; echo $a'; echo $a

또는:

a=1 command eval myfunction

myfunction함수 인 사용 또는 설정 $a잠재적 호출 command eval.

때문 정말 간과했다 ksh(사양은 대부분의 기반이되는) 그것을 구현하지 않았다 (그리고 AT & T를 ksh하고 zsh그것을 구현하는 두, 대부분의 껍질을 제외하고, 현재 아직도 할), 그러나. 동작은 다음과 같이 쉘마다 다릅니다.

a=0; a=1 command eval a=2; echo "$a"

그러나. local이를 지원하는 쉘에서 사용 하는 것이 로컬 범위를 구현하는보다 안정적인 방법입니다.


이상하게도 POSIX에서 지시 한대로 대시, pdksh 및 bash로 ksh 93u가 아닌 의 기간 동안 만 IFS=: command eval …설정 IFS합니다 eval. ksh가 홀수 비준수 one-out 인 것을 보는 것은 드문 일입니다.
Gilles 'SO- 악마 그만'

12

Kernighan과 Pike의 "The Unix Programming Environment"에서 가져온 표준 저장 및 복원 :

#!/bin/sh
old_IFS=$IFS
IFS="something_new"
some_program_or_builtin
IFS=${old_IFS}

2
감사합니다. +1 예, 저는이 옵션을 알고 있지만, 무슨 의미인지 알면 "클리너"옵션이 있는지 알고 싶습니다.
iruvar

세미콜론으로 한 줄에 넣을 수는 있지만 더 깨끗하지는 않습니다. 표현하고자하는 모든 것이 특별한 구문 지원을 가지고 있다면 좋을지 모르지만 코딩 대신 목공이나 섬프 텐트를 배워야 할 것입니다.)
msw

9
$IFS이전에 설정하지 않은 경우 올바르게 복원 되지 않습니다.
Stéphane Chazelas

2
설정되지 않은 경우 Bash는 wiki.bash-hackers.org/syntax/expansion/…에$'\t\n'' ' 설명 된 대로으로 처리합니다 .
davide

2
@ davide, 그럴 것입니다 $' \t\n'. 사용되는 공간이 먼저 있어야합니다 "$*". 모든 Bourne 형 쉘에서 동일합니다.
Stéphane Chazelas

8

스크립트를 함수에 넣고 명령 행 인수를 전달하여 해당 함수를 호출하십시오. IFS가 로컬로 정의되었으므로 IFS가 변경되면 글로벌 IFS에 영향을 미치지 않습니다.

main() {
  local IFS='/'

  # the rest goes here
}

main "$@"

6

이 명령의 경우 :

IFS=$'\n' a=($str)

대안적인 해결책이 있습니다 : 첫 번째 할당 ( IFS=$'\n')에 실행할 명령 (함수)을주는 것 :

$ split(){ a=( $str ); }
$ IFS=$'\n' split

그러면 IFS가 환경에 호출 분할을 배치하지만 현재 환경에서는 유지되지 않습니다.

이것은 또한 항상 위험한 eval의 사용을 피합니다.


POSIX 모드에있을 때 ksh93 및 mksh 및 bash 및 zsh에서는 POSIX에서 요구하는대로 계속 $IFS설정되어 $'\n'있습니다.
Stéphane Chazelas

4

@helpermethod의 제안 된 답변은 확실히 흥미로운 접근법입니다. 그러나 BASH 로컬 변수 범위가 호출자에서 호출 된 함수로 확장되기 때문에 약간의 함정입니다. 따라서 main ()에서 IFS를 설정하면 해당 값이 main ()에서 호출 된 함수에 유지됩니다. 예를 들면 다음과 같습니다.

#!/usr/bin/env bash
#
func() {
  # local IFS='\'

  local args=${@}
  echo -n "$FUNCNAME A"
  for ((i=0; i<${#args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${args[$i]}"
  done
  echo

  local f_args=( $(echo "${args[0]}") )
  echo -n "$FUNCNAME B"
  for ((i=0; i<${#f_args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${f_args[$i]}  "
  done
  echo
}

main() {
  local IFS='/'

  # the rest goes here
  local args=${@}
  echo -n "$FUNCNAME A"
  for ((i=0; i<${#args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${args[$i]}"
  done
  echo

  local m_args=( $(echo "${args[0]}") )
  echo -n "$FUNCNAME B"
  for ((i=0; i<${#m_args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${m_args[$i]}  "
  done
  echo

  func "${m_args[*]}"
}

main "$@"

그리고 출력 ...

main A[0]: ick/blick/flick
main B[0]: ick  [1]: blick  [2]: flick
func A[0]: ick/blick/flick
func B[0]: ick  [1]: blick  [2]: flick

main ()으로 선언 된 IFS가 func ()에서 여전히 범위 내에 있지 않으면, 배열은 func () B에서 올바르게 구문 분석되지 않았을 것입니다. func ()에서 첫 번째 행의 주석 처리를 제거하면 다음과 같은 결과가 나타납니다.

main A[0]: ick/blick/flick
main B[0]: ick  [1]: blick  [2]: flick
func A[0]: ick/blick/flick
func B[0]: ick/blick/flick

IFS가 범위를 벗어나면 얻을 수있는 것입니다.

훨씬 더 나은 솔루션 IMHO는 글로벌 / 로컬 수준에서 IFS에 대한 변경 또는 의존을 포기하는 것입니다. 대신, 새로운 쉘을 생성하고 거기에 IFS를 넣습니다. 예를 들어 main ()에서 func ()를 다음과 같이 호출하는 경우 백 슬래시 필드 구분 기호를 사용하여 배열을 문자열로 전달합니다.

func $(IFS='\'; echo "${m_args[*]}")

... IFS 로의 변경은 func ()에 반영되지 않습니다. 배열은 문자열로 전달됩니다.

ick\blick\flick

... func () 내부에서 IFS는 func ()에서 로컬로 변경되지 않는 한 여전히 "/"(main ()에 설정된대로)입니다.

IFS 변경 사항을 격리하는 방법에 대한 자세한 내용은 다음 링크를 참조하십시오.

bash 배열 변수를 줄 바꿈으로 구분 된 문자열로 어떻게 변환합니까?

IFS로 배열 할 배쉬 문자열

일반적인 쉘 스크립트 프로그래밍을위한 힌트와 팁- "서브 쉘 사용에 대한주의 사항 ..."


참으로 흥미로운 ...
iruvar

"IFS로 배열 할 문자열" IFS=$'\n' declare -a astr=(...)완벽한 감사합니다!
물병 자리 힘

1

이 질문의 스 니펫 :

IFS=$'\n' a=($str)

왼쪽에서 오른쪽으로 평가되는 두 개의 개별 전역 변수 할당으로 해석되며 다음과 같습니다.

IFS=$'\n'; a=($str)

또는

IFS=$'\n'
a=($str)

전역 IFS이 수정 된 이유와 $str배열 요소로 의 단어 분리가 새로운 값인를 사용하여 수행 된 이유를 설명합니다 IFS.

서브 쉘을 사용하여 IFS다음과 같이 수정 의 영향을 제한하려는 유혹이있을 수 있습니다 .

str="value 0:value 1"
a=( old values )
( # Following code runs in a subshell
 IFS=":"
 a=($str)
 printf 'Subshell IFS: %q\n' "${IFS}"
 echo "Subshell: a[0]='${a[0]}' a[1]='${a[1]}'"
)
printf 'Parent IFS: %q\n' "${IFS}"
echo "Parent: a[0]='${a[0]}' a[1]='${a[1]}'"

그러나 수정 사항은 a하위 셸로 제한됩니다.

Subshell IFS: :
Subshell: a[0]='value 0' a[1]='value 1'
Parent IFS: $' \t\n'
Parent: a[0]='old' a[1]='values'

다음으로, @msw 의이 이전 답변 의 솔루션을 사용하여 IFS를 저장 / 복원 하거나 @helpermethod가 제안한local IFS 함수 내부를 사용하려고 합니다. 그러나 곧, 당신은 모든 종류의 문제에 직면하고 있음을 알 수 있습니다. 특히 당신이 스크립트를 잘못 호출하는 것에 대해 강력 해야하는 도서관 저자라면 :

  • IFS처음에 설정이 해제 되면 어떻게 되나요?
  • 우리가 set -u(일명 set -o nounset) 과 달리면 어떨까요?
  • IFS통해 읽기 전용으로 만들어진 경우 어떻게 declare -r IFS합니까?
  • 재귀 및 / 또는 비동기 실행 ( trap핸들러 와 같은) 작업을 위해 저장 / 복원 메커니즘이 필요한 경우 어떻게해야 합니까?

IFS를 저장 / 복원하지 마십시오. 대신 임시 수정을 고수하십시오.

  • 변수 수정을 단일 명령, 내장 또는 함수 호출로 제한하려면을 사용하십시오 IFS="value" command.

    • 특정 문자를 분할하여 여러 변수를 읽으려면 ( :아래 예제로 사용) 다음을 사용하십시오.

      IFS=":" read -r var1 var2 <<< "$str"
    • 배열 사용 (이 대신 할로 읽으려면 array_var=( $str )) :

      IFS=":" read -r -a array_var <<< "$str"
  • 변수 수정의 효과를 서브 쉘로 제한하십시오.

    • 배열의 요소를 쉼표로 구분하여 출력하려면

      (IFS=","; echo "${array[*]}")
    • 문자열로 캡처하려면 :

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

0

가장 직접적인 해결책은 $IFSmsw의 답변과 같이 원본의 사본을 만드는 것입니다. 그러나이 솔루션은 빈 문자열과 동일한 세트 IFSIFS세트를 구분하지 않으므로 많은 응용 프로그램에서 중요합니다. 이 차이점을 포착하는보다 일반적인 해결책은 다음과 같습니다.

# Functions taking care of IFS
set_IFS(){
    if [ -z "${IFS+x}" ]; then
        IFS_ori="__unset__"
    else
        IFS_ori="$IFS"
    fi
    IFS="$1"
}
reset_IFS(){
    if [ "${IFS_ori}" == "__unset__" ]; then
        unset IFS
    else
        IFS="${IFS_ori}"
    fi
}

# Example of use
set_IFS "something_new"
some_program_or_builtin
reset_IFS
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.