Como obter o último argumento para uma função / bin / sh

11

Qual é a melhor maneira de implementar print_last_arg ?

#!/bin/sh

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

print_last_arg foo bar baz
# baz

(Se isso fosse, digamos, #!/usr/bin/zsh em vez de #!/bin/sh , eu saberia o que fazer. Meu problema é encontrar uma maneira razoável de implementar isso para #!/bin/sh .)

EDIT: O acima é apenas um exemplo bobo. Meu objetivo não é imprimir o último argumento, mas sim ter uma maneira de se referir ao último argumento dentro de uma função de shell.

EDIT2: Peço desculpas por uma pergunta tão pouco clara. Espero acertar desta vez.

Se isso fosse /bin/zsh em vez de /bin/sh , eu poderia escrever algo assim

#!/bin/zsh

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

A expressão $argv[$#] é um exemplo do que descrevi em meu primeiro EDIT como uma maneira de se referir ao último argumento dentro de uma função do shell .

Portanto, eu deveria ter escrito meu exemplo original assim:

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

... para deixar claro que o que eu estou procurando é uma coisa menos horrível para colocar à direita da tarefa.

Note, no entanto, que em todos os exemplos, o último argumento é acessado não-destrutivamente . IOW, o acesso do último argumento deixa os argumentos posicionais como um todo inalterados.

    
por kjo 22.01.2016 / 20:20

11 respostas

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

... imprimirá a string the last arg is seguido por espaço , o valor do último argumento e ; newline > se houver pelo menos 1 argumento, ou então, para zero argumentos, ele não imprimirá nada.

Se você fez:

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

... então você atribuiria o valor do último argumento à variável shell $lastarg se houver pelo menos 1 argumento, ou você não faria nada. De qualquer forma, você faria isso com segurança, e deveria ser portátil até mesmo para a casca de Olde Bourne, eu acho.

Aqui está outro que funcionaria de forma semelhante, embora exija copiar todo o array arg duas vezes (e requer um printf em $PATH para o shell Bourne) :

if   [ "${1+:}" ]
then set "$#" "$@" "$@"
     shift    "$1"
     printf %s\n "$1"
     shift
fi
    
por 23.01.2016 / 08:32
3

Aqui está uma maneira simplista:

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

(atualizado com base no argumento de @ cuonglm de que o original falhou quando não passou nenhum argumento; isso agora ecoa uma linha em branco - mude esse comportamento na cláusula else se desejado)

    
por 22.01.2016 / 20:29
3

Dado o exemplo do post de abertura (argumentos posicionais sem espaços):

print_last_arg foo bar baz

Para o padrão IFS=' \t\n' , que tal:

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

Para uma expansão mais segura de "$*" , defina IFS (per @ StéphaneChazelas):

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

Mas os itens acima falharão se seus argumentos posicionais puderem conter espaços. Nesse caso, use isso:

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

Observe que essas técnicas evitam o uso de eval e não têm efeitos colaterais.

Testado em shellcheck.net

    
por 22.01.2016 / 22:16
2

POSIXly:

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

printf '%s\n' "$1"

(Essa abordagem também funciona no shell Bourne antigo)

Com outras ferramentas padrão:

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

(Isso não funcionará com o antigo awk , que não tem ARGV )

    
por 23.01.2016 / 08:27
2

Isso deve funcionar com qualquer shell compatível com POSIX e também funcionará com o shell Bouris do Solaris pré-POSIX:

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

e aqui está uma função baseada na mesma abordagem:

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

PS: não me diga que eu esqueci as chaves ao redor do corpo da função; -)

    
por 23.01.2016 / 12:35
2

De "Unix - Perguntas frequentes"

(1)

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

Se o número de argumentos puder ser zero, o argumento zero $0 (geralmente o nome do script) será atribuído a $last . Essa é a razão para o if.

(2)

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

(3)

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

Para evitar imprimir uma linha vazia quando não houver argumentos, substitua o echo "$last" para:

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

Uma contagem de zero argumentos é evitada por if [ $# -gt 0 ] .

Esta não é uma cópia exata do que está no link da página, algumas melhorias foram adicionadas.

    
por 23.01.2016 / 15:16
2

Embora essa pergunta tenha pouco mais de dois anos, pensei em compartilhar uma opção de compacto.

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

Vamos rodar

print_last_arg foo bar baz
baz

Bash expansão de parâmetros do shell .

Editar

Ainda mais conciso: echo "${@: -1}"

(Ocupe-se do espaço)

Source

Testado no macOS 10.12.6, mas também deve retornar o último argumento na maioria dos sabores * nix disponíveis ...

Causa muito menos ¯\_(ツ)_/¯

    
por 11.04.2018 / 17:23
0

Isso deve funcionar em todos os sistemas com perl instalado (portanto, a maioria dos UNICES):

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

O truque é usar printf para adicionar perl após cada argumento do shell e, em seguida, -0 $l switch, que define seu separador de registro como NULL. Em seguida, iteramos a entrada, removemos o END e salvamos cada linha terminada com NULL como }{ . O bloco %code% (que é o que o %code% é) será executado depois que toda a entrada tiver sido lida, então a última "linha" será impressa: o último argumento do shell.

    
por 23.01.2016 / 12:58
0

Aqui está uma versão usando recursão. Não tenho ideia de como isso é compatível com POSIX ...

print_last_arg()
{
    if [ $# -gt 1 ] ; then
        shift
        echo $( print_last_arg "$@" )
    else
        echo "$1"
    fi
}
    
por 23.01.2016 / 23:08
0

Um conceito simples, sem aritmética, sem loop, sem eval , apenas funciona.
Lembre-se que o shell Bourne não tinha aritmética (% externo necessárioexpr). Se quiser obter uma livre aritmética, eval free choice, esta é uma opção. A necessidade de funções significa SVR3 ou superior (sem sobregravação de parâmetros). Veja abaixo uma versão mais robusta com 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.

Essa estrutura para chamar printlast é fixa , os argumentos precisam ser configurados na lista de argumentos de shell $1 , $2 , etc. (a pilha de argumentos) e a chamada feito como determinado.

Se a lista de argumentos precisar ser alterada, basta defini-los:

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

Ou crie uma função mais fácil de usar ( getlast ) que possa permitir argumentos genéricos (mas não tão rápido, os argumentos são passados duas vezes).

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

Por favor, note que argumentos (de getlast , ou todos incluídos em $@ para printlast) podem ter espaços, novas linhas, etc. Mas não NUL.

Melhor

Esta versão não imprime 0 quando a lista de argumentos está vazia, e use o printf mais robusto (volte para echo se external printf não estiver disponível para shells antigos).

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

Usando EVAL.

Se o shell Bourne for ainda mais antigo e não houver funções, ou se, por algum motivo, o uso de eval for a única opção:

Para imprimir o valor:

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

Para definir o valor em uma variável:

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

Se isso precisa ser feito em uma função, os argumentos precisam ser copiados para a função:

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.
    
por 23.01.2016 / 08:43
0

Este comentário sugeriu usar o muito elegante:

echo "${@:$#}"
    
por 29.10.2018 / 11:23