/ bin / sh 함수에 마지막 인수를 얻는 방법


11

구현하는 더 좋은 방법은 무엇입니까 print_last_arg?

#!/bin/sh

print_last_arg () {
    eval "echo \${$#}"  # this hurts
}

print_last_arg foo bar baz
# baz

(이것이 #!/usr/bin/zsh대신 #!/bin/sh해야 할 일을 알고 있다면 대신 내 문제는 이것을 구현하는 합리적인 방법을 찾는 것입니다 #!/bin/sh.)

편집 : 위의 바보 같은 예입니다. 내 목표는 마지막 인수 를 인쇄 하는 것이 아니라 셸 함수 내에서 마지막 인수를 참조하는 방법을 갖는 것입니다.


EDIT2 : 나는 분명하지 않은 단어에 대해 사과드립니다. 나는 이번에 그것을 올바르게 얻을 수 있기를 바랍니다.

이것이 /bin/zsh대신에 이런 식으로 /bin/sh쓸 수 있습니다.

#!/bin/zsh

print_last_arg () {
    local last_arg=$argv[$#]
    echo $last_arg
}

이 표현식 $argv[$#]쉘 함수 내에서 마지막 인수를 참조하는 방법으로 첫 번째 EDIT에서 설명한 내용의 예입니다 .

따라서 실제로 다음과 같이 원래 예제를 작성해야합니다.

print_last_arg () {
    local last_arg=$(eval "echo \${$#}")   # but this hurts even more
    echo $last_arg
}

... 나중에있는 것이 과제의 오른쪽에 두는 것이 덜 끔찍한 것임을 분명히하기 위해.

그러나 모든 예제에서 마지막 인수는 비파괴 적으로 액세스 됩니다. 마지막 인수에 접근하면 위치 인수는 전체적으로 영향을받지 않습니다.


unix.stackexchange.com/q/145522/117549 는 #! / bin / sh의 다양한 가능성을 지적합니다. 범위를 좁힐 수 있습니까?
Jeff Schaller

나는 편집을 좋아하지만 아마도 마지막 논증을 참조하는 비파괴적인 수단을 제공하는 대답이 있다는 것을 알았을 것입니다 ...? 당신은 동일시해서는 안 var=$( eval echo \${$#})eval var=\${$#}두 사람이 모두 아무것도 -.
mikeserv

1
마지막 메모는 확실하지 않지만 지금까지 제공된 거의 모든 답변은 실행중인 스크립트 인수를 유지한다는 의미에서 파괴적이지 않습니다. 만 shift하고 set -- ...그들도 무해 함수에서 사용하지 않는 기반 솔루션은 파괴 될 수 있습니다.
jlliagre

@jlliagre-그러나 여전히 메인에서 파괴적입니다-그들은 처분 할 수있는 컨텍스트를 생성해야 발견 할 수 있습니다. 그러나 ... 어쨌든 두 번째 컨텍스트를 얻는다면 인덱스를 만들 수있는 이유는 무엇입니까? 작업용 도구를 사용하는 데 문제가 있습니까? 쉘 확장을 확장 가능한 입력으로 해석하는 것은 평가의 일입니다. 보장 된 안전한 가치 eval "var=\${$#}"var=${arr[evaled index]}제외하고 비교할 때 크게 다른 점은 없습니다 $#. 왜 전체 집합을 복사 한 다음 직접 색인을 생성 할 수있을 때 파괴합니까?
mikeserv

1
@mikeserv 쉘의 주요 부분에서 수행되는 for 루프는 모든 인수를 변경하지 않고 그대로 둡니다. 나는 모든 인수를 반복하는 것이 매우 최적화되지 않은 것에 동의합니다. 특히 수천 개의 인수가 쉘에 전달되면 올바른 색인으로 마지막 인수에 직접 액세스하는 것이 가장 좋은 대답이라는 데 동의합니다 ) 그러나 그 이상으로, 실제로 파괴적인 것은 없으며 여분의 맥락이 만들어지지 않습니다.
jlliagre

답변:


1
eval printf %s${1+"'\n' \"the last arg is \${$#"\}\"}

... 문자열 the last arg is뒤에 <space> , 마지막 인수의 값, 그리고 적어도 하나의 인수가 있으면 후행 <newline> 을 인쇄하거나 인수가 0 인 경우 아무 것도 인쇄하지 않습니다.

당신이했다면 :

eval ${1+"lastarg=\${$#"\}}

... $lastarg적어도 하나의 인수가있는 경우 마지막 인수의 값을 쉘 변수 에 지정하거나 전혀 수행하지 않습니다. 어느 쪽이든, 당신은 안전하게 그것을 할 것이고, Olde Bourne 껍질조차도 휴대 할 수 있어야한다고 생각합니다.

다음은 비슷하게 작동하는 또 다른 방법이지만 전체 arg 배열을 두 번 복사 해야하며 Bourne 쉘의 경우 printfin이 필요합니다$PATH .

if   [ "${1+:}" ]
then set "$#" "$@" "$@"
     shift    "$1"
     printf %s\\n "$1"
     shift
fi

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
terdon

2
참고로, 첫 번째 제안은 인수가 없으면 "잘못된 대체"오류와 함께 기존 bourne 쉘에서 실패합니다. 나머지는 모두 예상대로 작동합니다.
jlliagre

@jillagre-감사합니다. 나는 그 최상위에 대해 확신하지 못했지만 다른 두 가지를 꽤 확신했습니다. 참조 인라인으로 인수에 액세스하는 방법을 설명하기위한 것입니다. 가급적이면 함수가 바로 열려있을 것입니다. ${1+":"} return왜냐하면 아무 것도 요청하지 않으면 어떤 기능을 수행하거나 어떤 종류의 부작용을 일으킬 수 있기 때문입니다. 수학도 마찬가지 eval입니다. 양의 정수를 확장 할 수 있다면 하루 종일 할 수 있습니다 . $OPTIND그것에게 좋습니다.
mikeserv

3

간단한 방법은 다음과 같습니다.

print_last_arg () {
  if [ "$#" -gt 0 ]
  then
    s=$(( $# - 1 ))
  else
    s=0
  fi
  shift "$s"
  echo "$1"
}

(인수가 전달되지 않았을 때 원본이 실패했다는 @cuonglm의 요점을 기반으로 업데이트되었습니다. 이제 빈 줄을 에코 else합니다. 원하는 경우 절의 동작을 변경하십시오 )


이것은 Solaris에서 / usr / xpg4 / bin / sh를 사용해야합니다. Solaris #! / bin / sh가 요구 사항인지 알려주십시오.
Jeff Schaller

이 질문은 내가 작성하려는 특정 스크립트가 아니라 일반적인 방법입니다. 나는 이것에 대한 좋은 요리법을 찾고 있습니다. 물론 휴대 성이 좋을수록 좋습니다.
kjo

Solaris / bin / sh에서 $ (()) 수학이 실패합니다. 필요한 경우 shift`expr $ #-1`을 사용하십시오.
Jeff Schaller

Solaris / bin / sh의 경우echo "$# - 1" | bc
Jeff Schaller

1
그것은 내가 쓰고 싶었던 것이지만, 내가 시도한 두 번째 쉘에서 실패했습니다-NetBSD는 분명히 그것을 지원하지 않는 Almquist 쉘입니다 :(
Jeff Schaller

3

오프닝 포스트의 예가 주어진 경우 (공백이없는 위치 인수) :

print_last_arg foo bar baz

기본값 IFS=' \t\n'은 어떻습니까?

args="$*" && printf '%s\n' "${args##* }"

보다 안전한 확장을 위해 "$*"IFS (@ StéphaneChazelas)를 설정하십시오.

( IFS=' ' args="$*" && printf '%s\n' "${args##* }" )

그러나 위치 인수에 공백이 포함될 수 있으면 위의 내용은 실패합니다. 이 경우 대신 다음을 사용하십시오.

for a in "$@"; do : ; done && printf '%s\n' "$a"

이러한 기술은 사용을 피하고 eval부작용이 없습니다.

shellcheck.net 에서 테스트


1
마지막 인수에 공백이 있으면 첫 번째는 실패합니다.
cuonglm

1
첫 번째는 POSIX 이전 버전에서도 작동하지 않았습니다.
cuonglm

@cuonglm 글쎄, 당신의 정확한 관찰이 지금 통합되었습니다.
AsymLabs

또한 첫 번째 문자 $IFS가 공백 이라고 가정합니다 .
Stéphane Chazelas

1
환경에 $ IFS가 없으면 달리 지정되지 않습니다. 그러나 split + glob 연산자 (확장 인용 부호 제외)를 사용할 때마다 거의 IFS를 설정해야하므로 IFS를 처리하는 한 가지 방법은 필요할 때마다 설정하는 것입니다. IFS=' '확장을 위해 사용되고 있음을 분명히하기 위해 여기에있는 것은 해가되지 않습니다 "$*".
Stéphane Chazelas

3

이 질문은 2 년이 넘었지만 조금 더 작은 옵션을 공유한다고 생각했습니다.

print_last_arg () {
    echo "${@:${#@}:${#@}}"
}

실행하자

print_last_arg foo bar baz
baz

배쉬 쉘 매개 변수 확장 .

편집하다

더 간결한 : echo "${@: -1}"

(공간을 생각하십시오)

출처

macOS 10.12.6에서 테스트되었지만 사용 가능한 대부분의 * nix 맛에 대한 마지막 인수도 반환해야합니다 ...

훨씬 아파 ¯\_(ツ)_/¯


이것이 정답입니다. 더 좋은 점은 다음과 같습니다 echo "${*: -1}"어떤 shellcheck약 불평하지 않습니다.
Tom Hale

4
이것은 평범한 POSIX sh에서는 작동하지 않으며 ${array:n:m:}확장입니다. (질문에 명시 적으로 언급 /bin/sh)
ilkkachu

2

POSIXly :

while [ "$#" -gt 1 ]; do
  shift
done

printf '%s\n' "$1"

(이 방법은 이전 Bourne 쉘에서도 작동합니다)

다른 표준 도구로 :

awk 'BEGIN{print ARGV[ARGC-1]}' "$@"

(이것은 없었던 old awk에서는 작동 하지 않습니다 ARGV)


여전히 Bourne 쉘이있는 곳에서는 awk없는 오래된 awk 일 수도 있습니다 ARGV.
Stéphane Chazelas

@ StéphaneChazelas : 동의합니다. 적어도 Brian Kernighan의 자체 버전과 함께 작동했습니다. 이상이 있습니까?
cuonglm

인수가 지워집니다. 그것들을 지키는 방법?.

@BinaryZebra : awk접근 방식을 사용 하거나 다른 for것과 마찬가지로 다른 평범한 루프를 사용 하거나 함수에 전달하십시오.
cuonglm

2

이것은 POSIX 호환 쉘에서 작동해야하며 이전 POSIX 레거시 Solaris Bourne 쉘에서도 작동합니다.

do=;for do do :;done;printf "%s\n" "$do"

다음은 동일한 접근 방식을 기반으로하는 기능입니다.

print_last_arg()
  if [ "$*" ]; then
    for do do :;done
    printf "%s\n" "$do"
  else
    echo
  fi

추신 : 함수 본문 주위에 중괄호를 잊어 버렸다고 말하지 마십시오. ;-)


2
함수 본문 주위에 중괄호를 잊어 버렸습니다. ;-).

@BinaryZebra 나는 당신에게 경고했다 ;-) 나는 그들을 잊지 않았다. 중괄호는 놀랍게도 선택 사항입니다.
jlliagre

1
@jlliagre I는 실제로 경고되었습니다 ;-) : P ...... 그리고 : 확실히!

구문 사양의 어느 부분이 이것을 허용합니까?
Tom Hale

@TomHale 쉘 문법 규칙은 함수 본문으로 사용 되는 문장 주위에 루프 in ...에서 누락 for과 중괄호를 허용하지 않습니다 if. 참조 for_clausecompound_commandpubs.opengroup.org/onlinepubs/9699919799 .
jlliagre

2

에서 "- 자주 묻는 질문 (FAQ) 유닉스"

(1)

unset last
if    [ $# -gt 0 ]
then  eval last=\${$#}
fi
echo  "$last"

인수 수가 0 일 수있는 경우 인수 0 $0(대개 스크립트 이름)이에 지정됩니다 $last. 그것이 if의 이유입니다.

(2)

unset last
for   last
do    :
done
echo  "$last"

(삼)

for     i
do
        third_last=$second_last
        second_last=$last
        last=$i
done
echo    "$last"

인수가 없을 때 빈 줄을 인쇄하지 않으려면 echo "$last"for를 바꾸십시오 .

${last+false} || echo "${last}"

인수 수는 0으로 피합니다 if [ $# -gt 0 ].

이것은 페이지에 링크 된 내용의 정확한 사본이 아니며 일부 개선 사항이 추가되었습니다.


0

이것은 perl설치된 대부분의 시스템에서 작동해야합니다 (대부분의 UNICES).

print_last_arg () {
    printf '%s\0' "$@" | perl -0ne 's/\0//;$l=$_;}{print "$l\n"'
}

트릭 사용하는 printfa를 위해 \0각 셀의 인수 후, 이후 perl-0NULL에 그 레코드 구분자를 설정 스위치. 그런 다음 입력을 반복하고 \0NULL로 끝나는 각 줄을로 저장합니다 $l. END블록 ((가)의 어떤 것을 }{마지막 쉘 인수 :입니다) 모든 입력이 마지막 "라인"을 인쇄 할 수 있도록 읽은 후 실행됩니다.


@ mikeserv 아, 사실, 마지막 인수 외에는 개행을 테스트하지 않았습니다. 편집 된 버전은 거의 모든 것이 작동하지만 간단한 작업에는 너무 복잡합니다.
terdon

예, 외부 실행 파일 (논리의 일부가 아닌 경우 )을 호출하는 것은 조금 무겁습니다. 점이에게 그 인자를 전달하는 경우 그러나 sed, awk또는 perl그것은 어쨌든 중요한 정보가 될 수 있습니다. 그래도 sed -z최신 GNU 버전 에서도 마찬가지입니다 .
mikeserv

왜 다운 보트를 받았습니까? 이것은 좋았다. .. 나는 생각했다?
mikeserv

0

재귀를 사용하는 버전이 있습니다. POSIX를 준수하는 방법을 모르겠습니다 ...

print_last_arg()
{
    if [ $# -gt 1 ] ; then
        shift
        echo $( print_last_arg "$@" )
    else
        echo "$1"
    fi
}

0

간단한 개념, 산술, 루프, 평가 , 기능 없음.
Bourne 쉘에는 산술이 없었 음을 기억하십시오 (external 필요 expr). 산술 무료, eval자유 선택을 원한다면 이것은 옵션입니다. 기능이 필요하다는 것은 SVR3 이상을 의미합니다 (매개 변수 덮어 쓰기 없음).
printf가있는보다 강력한 버전은 아래를 참조하십시오.

printlast(){
    shift "$1"
    echo "$1"
}

printlast "$#" "$@"          ### you may use ${1+"$@"} here to allow
                             ### a Bourne empty list of arguments,
                             ### but wait for a correct solution below.

호출이 구성 printlast되어 고정 인자 쉘 인수 목록에서 설정 될 필요가 $1, $2등 (인수 스택) 및 주어진 호출 할.

인수 목록을 변경해야하는 경우 다음과 같이 설정하십시오.

set -- o1e t2o t3r f4u
printlast "$#" "$@"

또는 getlast일반 인수를 허용 할 수있는 사용하기 쉬운 함수 ( )를 작성하십시오 (단, 인수가 두 번 전달되는 것은 아닙니다).

getlast(){ printlast "$#" "$@"; }
getlast o1e t2o t3r f4u

인수 (의 getlast또는 $@printlast에 포함 된 모든 인수 )에는 공백, 줄 바꿈 등이있을 수 있습니다. 그러나 NUL은 아닙니다.

보다 나은

이 버전은 0인수 목록이 비어 있으면 인쇄되지 않으며 보다 강력한 printf를 사용합니다 ( 이전 쉘에서 echo외부 printf를 사용할 수없는 경우로 폴백 ).

printlast(){ shift  "$1"; printf '%s' "$1"; }
getlast  ()  if     [ $# -gt 0 ]
             then   printlast "$#" "$@"
                    echo    ### optional if a trailing newline is wanted.
             fi
### The {} braces were removed on purpose.

getlast 1 2 3 4    # will print 4

EVAL 사용

Bourne 쉘이 이전 버전이고 기능이 없거나 eval을 사용하는 것이 유일한 옵션 인 경우 :

값을 인쇄하려면

if    [ $# -gt 0 ]
then  eval printf "'1%s\n'" \"\$\{$#\}\"
fi

변수에 값을 설정하려면

if    [ $# -gt 0 ]
then  eval 'last_arg='\"\$\{$#\}\"
fi

함수에서 수행해야하는 경우 인수를 함수에 복사해야합니다.

print_last_arg () {
    local last_arg                ### local only works on more modern shells.
    eval 'last_arg='\"\$\{$#\}\"
    echo "last_arg3=$last_arg"
}

print_last_arg "$@"               ### Shell arguments are sent to the function.

더 낫지 만 루프를 사용하지 않는다고 말합니다. 배열 사본 은 루프 입니다. 그리고 두 가지 기능으로 두 개의 루프를 사용합니다 . 또한-전체 배열을 반복하는 것이 색인을 생성하는 것보다 선호 해야하는 이유가 eval있습니까?
mikeserv

1
당신은 어떤 보는가 for loop또는 while loop?


1
표준에 따르면 모든 코드에는 루프 echo "Hello"가 있으며 CPU가 루프에서 메모리 위치를 읽어야하기 때문에 루프가 있습니다. 다시 : 내 코드에는 루프가 추가되지 않았습니다.

1
나는 정말 좋고 나쁜 것에 대한 당신의 의견에 관심이 없으며, 이미 여러 번 잘못 입증되었습니다. 다시 : 내 코드는 더 없다 for, while또는 until루프를. 당신은 어떤 보는가 for while또는 until코드로 작성 루프를? 아니요, 사실을 받아들이고 받아들이고 배우십시오.

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