Eu tenho um comando complexo que I'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 poder passar vários nomes de entrada para o script. Qual'é a maneira correcta de o fazer?
E, claro, eu quero lidar com nomes de arquivo com espaços neles.
Utilize "$@"
para representar todos os argumentos:
for var in "$@"
do
echo "$var"
done
Isto irá iterar sobre cada argumento e imprimi-lo em uma linha separada. $@ comporta-se como $*, exceto que quando citados os argumentos são quebrados corretamente se houver espaços neles:
sh test.sh 1 2 '3 4'
1
2
3 4
Re-escrever uma resposta já eliminada resposta por VonC.
A resposta sucinta de Robert Gamble lida diretamente com a pergunta.
Esta amplifica em alguns assuntos com nomes de arquivo contendo espaços.
Veja também: ${1:+"$@"} in /bin/sh
Tese básica: "$@"
está correta, e $*
(não cotada) está quase sempre errada.
Isto porque "$@"
funciona bem quando os argumentos contêm espaços, e
funciona da mesma forma que $*
quando não funciona.
Em algumas circunstâncias, "$*"
também funciona bem, mas "$@"
geralmente (mas não
sempre) trabalha nos mesmos lugares.
Não cotados, $@
e $*
são equivalentes (e quase sempre errados).
Então, qual é a diferença entre $*
, $@
, "$*"
, e "$@"
? Todos eles estão relacionados com "todos os argumentos para a concha", mas fazem coisas diferentes. Quando não citados, $*
e $@
fazem a mesma coisa. Eles tratam cada 'palavra' (sequência de espaços não citados) como um argumento separado. As formas citadas são bem diferentes, no entanto: "$*"
trata a lista de argumentos como uma única string separada por espaço, enquanto "$@"
trata os argumentos quase exatamente como eles eram quando especificados na linha de comando.
"$@"
expande para nada quando não há argumentos posicionais; "$*"
expande para uma string &mdash 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 depois
passe-os para outros comandos, então às vezes você precisa de comandos não-padronizados
ferramentas para ajudar. (Ou você deve usar arrays, com cuidado: "${array[@]}"
comporta-se analogamente a "$@"
.)
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
$
Porque é que isso não funciona?
Não funciona porque os processos de citação de conchas antes de se expandirem
variáveis.
Então, para que a shell preste atenção às aspas embutidas em $var
,
você tem que 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 muito complicado quando você tem nomes de arquivos como "Ele disse, "Não faças isso!" (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
$
As conchas (todas elas) não facilitam particularmente o manuseio de tais
coisas, então (curiosamente) muitos programas Unix não fazem um bom trabalho de
a lidar com eles.
No Unix, um nome de arquivo (componente único) pode conter quaisquer caracteres, exceto
e NUL '\0'``. No entanto, as conchas encorajam fortemente a ausência de espaços ou novas linhas ou separadores. em qualquer lugar em um caminho nomes. É também por isso que os nomes de arquivos Unix padrão não contêm espaços, etc. Ao lidar com nomes de arquivos que podem conter espaços e outros personagens problemáticos, você tem que ser extremamente cuidadoso, e eu achei há muito tempo que eu precisava de um programa que não é padrão no Unix. Eu o chamo de
escape(a versão 1.1 foi datada de 1989-08-23T16:01:45Z). Aqui está um exemplo do
escapeem uso - com o sistema de controle SCCS. É um script de capa que faz um
delta(pense *check-in*) e um
get(pensa *check-out*). Vários argumentos, especialmente
-y(a razão pela qual você fez a mudança) conteria espaços em branco e linhas novas. Note que o script data de 1992, então ele usa back-ticks ao invés de
$(cmd ...)notação e não utiliza
#!/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 "$@"
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"
(Provavelmente eu não usaria a fuga tão bem hoje em dia - ela é
não necessário com o argumento -e', por exemplo - mas, em geral, isto é um dos meus scripts mais simples utilizando o
escape). O programa
escape' simplesmente produz seus argumentos, como o echo'. mas assegura que os argumentos são protegidos para uso com (um nível de "eval"; eu tenho um programa que fez shell remoto execução, e que era necessário para escapar da saída do
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 antiga: versão 1.1 datada de 1987-01-27T14:35:49).
É mais útil na depuração de scripts, pois pode ser ligado a um
linha de comando para ver que argumentos são realmente passados ao 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 notas de "$@"
, aqui está mais um exemplo:
$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ 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
$
Note que nada preserva os espaços em branco originais entre os *
e */*
na linha de comando. Note também que você pode alterar os 'argumentos da linha de comando' na shell utilizando:
set -- -new -opt and "arg with space"
Isto define 4 opções, '-novo', '-opt', 'e', e 'arg com espaço'.
]
Hmm, isso é um longo resposta - talvez exegese seja o melhor termo.
Código fonte para escape
disponível a pedido (e-mail para o primeiro ponto do nome
último nome no gmail dot com).
O código fonte do `al' é incrivelmente simples:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
É tudo. É equivalente ao script test.sh' que Robert Gamble mostrou, e poderia ser escrito como uma função shell (mas funções shell não existiam na versão local de Bourne shell quando eu escrevi
al').
Note também que você pode escrever `al' como um simples script shell:
[ $# != 0 ] && printf "%s\n" "$@"
O condicional é necessário para que não produza nenhum resultado quando passado sem argumentos. O comando `printf' irá produzir uma linha em branco com apenas o argumento de formatação de string, mas o programa em C não produz nada.
Note que a resposta de Robert está correta, e funciona também em sh
. Você pode (portabilidade) simplificá-la ainda mais:
for i in "$@"
é equivalente a:
for i
Isto é, não precisas de nada!
Teste ($
é um prompt de comando):
$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d
Eu li sobre isso pela primeira vez no Unix Programming Environment de Kernighan e Pike.
Em bash',
ajuda para' documentar isto:
"para NOME [em PALAVRAS ... ;] do COMMANDS; done
Se
'in WORDS ...;'`` não está presente, então
'in "$@"'' é assumido.