쉘 매개변수를 어떻게 바꾸나요?

쉘 매개변수를 어떻게 바꾸나요?

"$@"나는 배열을 사용하여 반전하는 것이 가능하다는 것을 알고 있습니다 .

arr=( "$@" )

그리고이 답변을 사용하세요, 배열을 뒤집습니다.

하지만 이를 위해서는 배열이 포함된 셸이 필요합니다.

도 가능 tac:

set -- $( printf '%s\n' "$@" | tac )

그러나 인수에 공백, 탭 또는 줄 $IFS바꿈 (기본값은 이라고 가정)이 포함되거나 와일드카드가 포함되어(와일드카드가 사전에 비활성화되지 않은 경우) 빈 요소가 제거되고 GNU tac명령( tail -rGNU 시스템 외부에서 사용됨)이 약간 더 이식성이 있는 경우 이는 중단됩니다. 그러나 일부 구현은 큰 입력으로 인해 실패합니다).

배열을 사용하지 않고 쉘 위치 매개변수를 이식 가능하게 되돌리고 매개변수에 공백, 개행 또는 와일드카드가 포함되어 있거나 비어 있을 수 있는 경우에도 해당 메소드가 작동하도록 하는 방법이 있습니까?

답변1

이식 가능하며 배열이 필요하지 않으며(위치 인수만 해당) 공백과 줄 바꿈을 사용할 수 있습니다.

flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

예:

$ set -- one "two 22" "three
> 333" four

$ printf '<%s>' "$@"; echo
<one><two 22><three
333><four>

$ flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

$ printf '<%s>' "$@"; echo
<four><three
333><two 22><one>

값은 flag확장을 제어합니다 ${flag-"$@"}. set 인 경우 (비어 있는 경우에도) flag값으로 확장됩니다 . flag따라서 flagis 인 flag=''경우 ${flag....}null로 확장되고 따옴표가 없기 때문에 셸에서 제거됩니다. 설정되지 않은 경우 flag의 값은 의 확장인 ${flag-"$@"}오른쪽 값으로 확장 되므로 모든 위치 인수가 됩니다(따옴표 포함, null 값은 제거되지 않음). 또한 변수는 결국 제거(설정 해제)되며 후속 코드에 영향을 주지 않습니다.-"$@"flag

답변2

배열을 임시 저장소로 사용하고 싶지 않으면 for루프를 사용할 수 있습니다.언제나불변의 정적 요소 세트를 반복합니다. 어떤 의미에서 우리는 루프를 사용할 수 있습니다그 자체목록을 역순으로 다시 작성하는 동안 위치 인수에 대한 임시 저장소 역할을 합니다.

이를 수행하려면 첫 번째 반복에서 목록을 지워야 합니다. 아래 코드는 간단한 플래그를 사용하여 이것이 필요한지 여부를 감지합니다. 목록이 지워지면 플래그가 전환됩니다.

flag=true
for value do
    if "$flag"; then
        set --
        flag=false
    fi

    set -- "$value" "$@"
done

불행하게도 위치 매개변수 목록이 실제로는 매우 느립니다.각 반복마다 다시 빌드( set -- some-list모든 위치 매개변수를 설정합니다). 셸은 bash1에서 10000 사이의 정수를 반전하는 데 약 50초가 걸리지만 zsh15초가 조금 넘게 걸립니다.

사용이삭의 속임수with (설정되지 않은 경우에만 확장됨 ${flag-"$@"})는 실제로 전체 실행 속도를 1분 50초(!) 25초 늦춥니다."$@"flagbashzsh

$flag나는 이것이 쉘이 테스트 및/또는 확장 확장을 수행하는 방법 "$@"에 대한 일부 구현 세부 사항 때문이라고 가정합니다 ${flag-"$@"}(쉘이 "$@"내부적으로 두 번 확장될 수도 있습니까?).


배열을 임시 저장소로 사용하도록 허용된다면(이것은 그렇지 않습니다.기준, 하지만 여전히 꽤가지고 다닐 수 있는우리는 일반적으로 스크립트를 작성하는 쉘을 알고 있으므로 $#위치 인수를 반복할 때 해당 값(위치 인수 수)을 인덱스로 사용하여 현재 값을 저장할 수 있습니다. 반복할 때마다 이 값을 줄이면 shift배열의 끝부터 처음까지 값을 삽입하는 효과가 있습니다.

에서 bash배열은 인덱스 0에서 시작하고 shift할당 후에 발생하므로 마지막 위치 인수는 0이 아닌 인덱스 1에 저장됩니다. 이는 에서 코드가 작동하는 방식에 영향을 주지 않으며 bash여전히 올바른 결과를 생성하지만 작동하게 만듭니다 zsh(기본적으로 1 기반 배열 인덱싱 사용).

암호:

tmp=()
for value do
    tmp[$#]=$value
    shift
done

set -- "${tmp[@]}"

bash또는 를 사용하면 zsh1에서 10000 사이의 정수를 반전시키는 데 약 0.6초가 걸립니다.

답변3

복사한 곳내 대답도착하다Bash - glob을 사용하여 역방향 파일 목록 인쇄, POSIX 방식으로 위치 인수 목록을 뒤집습니다.

eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"

또는 몇 줄로 좀 더 명확하게 설명하자면 다음과 같습니다.

eval "set -- $(
  awk '
    BEGIN {
      for (i = ARGV[1]; i; i--)
        printf " \"${" i "}\""
    }' "$#"
)"

예를 들어, 3개의 요소가 있을 때 해석할 쉘 코드를 awk생성하는 데 도움이 되는 아이디어입니다 .set -- "${3}" "${2}" "${1}"eval"$@"

큰 목록의 경우 쉘 루프, 특히 각 반복에서 목록을 다시 작성하는 루프를 사용하는 것보다 훨씬 빠를 수 있습니다. 이 awk코드는 동일한 출력을 제공하는 쉘 루프로 대체될 수 있습니다(@mosvy가 주석에서 제안한 것처럼). 그러나 bash5+gawk4.1을 사용한 테스트에서는 매우 짧은 목록을 제외하면 여전히 두 배 느립니다.

배열을 뒤집기 위해 명시적으로 설계된 매개변수 플래그를 zsh사용할 수 있습니다 .Oa

set -- "${(Oa)@}"

내 시스템(@Kusalananda보다 약간 느림)과 bash5 + gawk4.2.1 로 얻은 위치 인수 목록을 사용하면 set $(seq 10000)eval방법은 0.4초가 걸리는 반면@쿠살라난다1분 정도 소요되며@ISAAC의2분이 소요됩니다( zsh방법 Oa은 약 2밀리초 소요).

busybox 1.30.1 sh에서는 awk이 시간이 각각 0.06초, 11초, 11초가 됩니다.

관련 정보