number - bash script example




Como iterar sobre argumentos em um script Bash (4)

Eu tenho um comando complexo que eu gostaria de fazer um script shell / bash de. Eu posso escrevê-lo em termos de $1 facilmente:

foo $1 args -o $1.ext

Eu quero ser capaz de passar vários nomes de entrada para o script. Qual é o caminho certo para fazer isso?

E, claro, quero manipular nomes de arquivos com espaços neles.


Note que a resposta de Robert está correta, e também funciona no sh . Você pode (portavelmente) simplificá-lo ainda mais:

for i in "[email protected]"

é equivalente a:

for i

Ou seja, você não precisa de nada!

Teste ( $ é prompt de comando):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "[email protected]"; do echo "$i"; done
a
b
spaces here
d

Eu li pela primeira vez sobre isso em Ambiente de Programação Unix por Kernighan e Pike.

Em bash , help for documentos isto:

for NAME [in WORDS ... ;] do COMMANDS; done

Se 'in WORDS ...;' não está presente, então 'in "[email protected]"' é assumido.


Para casos simples, você também pode usar shift . Ele trata a lista de argumentos como uma fila, cada shift lança o primeiro argumento, o número de cada argumento restante é decrementado.

#this prints all arguments
while test $# -gt 0
do
    echo $1
    shift
done

Você também pode acessá-los como um elemento de matriz, por exemplo, se você não deseja iterar através de todos eles

argc=$#
argv=([email protected])

for (( j=0; j<argc; j++ )); do
    echo ${argv[j]}
done

Reescreva de uma answer agora excluída pelo VonC .

A resposta sucinta de Robert Gamble lida diretamente com a questão. Este amplifica em alguns problemas com nomes de arquivos contendo espaços.

Veja também: $ {1: + "$ @"} em / bin / sh

Tese básica: "[email protected]" está correto, e $* (sem aspas) está quase sempre errado. Isso ocorre porque "[email protected]" funciona bem quando os argumentos contêm espaços e funciona da mesma forma que $* quando não contêm. Em algumas circunstâncias, "$*" é bom, mas "[email protected]" geralmente (mas nem sempre) funciona nos mesmos locais. Não cotado, [email protected] e $* são equivalentes (e quase sempre errados).

Então, qual é a diferença entre $* , [email protected] , "$*" e "[email protected]" ? Eles estão todos relacionados com "todos os argumentos para o shell", mas eles fazem coisas diferentes. Quando sem aspas, $* e [email protected] fazem a mesma coisa. Eles tratam cada palavra (seqüência de espaços não brancos) como um argumento separado. Os formulários citados são bem diferentes, embora: "$*" trate a lista de argumentos como uma única string separada por espaços, enquanto "[email protected]" trata os argumentos quase exatamente como eram quando especificados na linha de comando. "[email protected]" expande para nada quando não há argumentos posicionais; "$*" expande para uma string vazia - e sim, há uma diferença, embora possa ser difícil percebê-la. Veja mais informações abaixo, após a introdução do comando (não-padrão) al .

Tese secundária: se você precisar processar argumentos com espaços e, em seguida, passá-los para outros comandos, às vezes precisará de ferramentas não padronizadas para ajudar. (Ou você deve usar arrays, cuidadosamente: "${array[@]}" se comporta de maneira análoga a "[email protected]" .)

Exemplo:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

Por que isso não funciona? Não funciona porque o shell processa cotações antes de expandir as variáveis. Então, para fazer com que o shell preste atenção às cotações incorporadas no $var , você precisa usar o eval :

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

Isso fica realmente complicado quando você tem nomes de arquivo como " He said, "Don't do this!" " (Com aspas e aspas duplas e espaços).

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

Os shells (todos eles) não facilitam muito o manuseio de tais coisas, então (curiosamente) muitos programas Unix não fazem um bom trabalho em lidar com eles. No Unix, um nome de arquivo (componente único) pode conter qualquer caractere, exceto barra e NUL '\0' . No entanto, os shells não incentivam espaços, novas linhas ou tabulações em nenhum dos nomes de caminho. É também por isso que os nomes de arquivos padrão do Unix não contêm espaços, etc.

Ao lidar com nomes de arquivos que podem conter espaços e outros caracteres problemáticos, você precisa ser extremamente cuidadoso, e descobri há muito tempo que precisava de um programa que não fosse padrão no Unix. Eu chamo de escape (versão 1.1 foi datada de 1989-08-23T16: 01: 45Z).

Aqui está um exemplo de escape em uso - com o sistema de controle SCCS. É um script de capa que faz tanto um delta (pense no check-in ) quanto um get (pense no check-out ). Vários argumentos, especialmente -y (a razão pela qual você fez a alteração) conteria espaços em branco e novas linhas. Note que o script data de 1992, então ele usa back-ticks em vez de $(cmd ...) e não usa #!/bin/sh na primeira linha.

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "[email protected]"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(Eu provavelmente não usaria escape tão completamente nos dias de hoje - não é necessário com o argumento -e , por exemplo - mas no geral, este é um dos meus scripts mais simples usando escape .)

O programa de escape simplesmente exibe seus argumentos, mais ou menos como echo , mas assegura que os argumentos sejam protegidos para uso com eval (um nível de eval ; eu tenho um programa que executou shell remoto e que precisava escapar da saída de escape ).

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

Eu tenho outro programa chamado al que lista seus argumentos um por linha (e é ainda mais antigo: versão 1.1 datada de 1987-01-27T14: 35: 49). É mais útil ao depurar scripts, pois ele pode ser conectado a uma linha de comando para ver quais argumentos são realmente passados ​​para o comando.

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ Adicionado: E agora, para mostrar a diferença entre as várias notações "[email protected]" , aqui está mais um exemplo:

$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

Observe que nada preserva os espaços em branco originais entre * e */* na linha de comando. Além disso, observe que você pode alterar os 'argumentos da linha de comando' no shell usando:

set -- -new -opt and "arg with space"

Isso define 4 opções, ' -opt ', ' -opt ', ' and ' e ' arg with space '.
]

Hmm, essa é uma resposta bem longa - talvez a exegese seja o melhor termo. Código fonte para escape disponível a pedido (email para firstname dot lastname no gmail dot com). O código fonte para al é incrivelmente simples:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

Isso é tudo. É equivalente ao script test.sh que Robert Gamble mostrou, e poderia ser escrito como uma função de shell (mas as funções de shell não existiam na versão local do shell Bourne quando eu escrevi al ).

Note também que você pode escrever tudo como um simples shell script:

[ $# != 0 ] && printf "%s\n" "[email protected]"

A condicional é necessária para que não produza saída quando não passar argumentos. O comando printf produzirá uma linha em branco apenas com o argumento de string de formato, mas o programa C não produz nada.





command-line