Reescreva uma resposta agora excluída pelo VonC .
A resposta sucinta de Robert Gamble lida diretamente com a questão. Este amplifica algumas questões com nomes de arquivos contendo espaços.
Veja também: $ {1: + "$ @"} em / bin / sh
Tese básica: "$@"
está correta e $*
(sem aspas) quase sempre está errada. Isso ocorre porque "$@"
funciona bem quando os argumentos contêm espaços e funciona da mesma maneira que $*
quando não. Em algumas circunstâncias, tudo "$*"
bem também, mas"$@"
geralmente (mas nem sempre) funciona nos mesmos lugares. Não citado $@
e $*
é equivalente (e quase sempre errado).
Então, qual é a diferença entre $*
, $@
, "$*"
, e "$@"
? Todos eles estão relacionados a 'todos os argumentos do shell', mas fazem coisas diferentes. Quando não citado, $*
e $@
faça a mesma coisa. Eles tratam cada 'palavra' (sequência de espaço não em branco) como um argumento separado. As formas citadas são bem diferentes, no entanto: "$*"
trata a lista de argumentos como uma única cadeia de caracteres separada por espaço, enquanto "$@"
trata os argumentos quase exatamente como eram quando especificados na linha de comando.
"$@"
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 passá-los para outros comandos, às vezes precisará de ferramentas não padrão para ajudá-lo. (Ou você deve usar matrizes com cuidado: "${array[@]}"
comporta - se de maneira análoga 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
$
Por que isso não funciona? Não funciona porque o shell processa aspas antes de expandir variáveis. Portanto, para que o shell preste atenção nas aspas incorporadas $var
, você deve usar 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 arquivos como " He said,
"Don't do this!"
" (com aspas e aspas e espaços duplos).
$ 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 o manuseio de tais coisas, então (engraçado) 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 lugar nos nomes de um caminho. É também por isso que os nomes padrão de arquivos 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 issoescape
(a versão 1.1 foi datada de 1989-08-23T16: 01: 45Z).
Aqui está um exemplo de escape
uso - com o sistema de controle SCCS. É um script de capa que faz um delta
(pense no check-in ) e um
get
(pense no check-out ). Vários argumentos, especialmente -y
(o motivo pelo qual você fez a alteração), conteriam espaços em branco e novas linhas. Observe que o script data de 1992, portanto, usa back-ticks em vez de
$(cmd ...)
notação 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 "$@"
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 a fuga tão completamente hoje em dia - não é necessário com o -e
argumento, por exemplo - mas, no geral, esse é um dos meus scripts mais simples usandoescape
).
O escape
programa simplesmente exibe seus argumentos, da mesma forma que echo
faz, mas garante que os argumentos sejam protegidos para uso com
eval
(um nível de eval
; eu tenho um programa que executou a shell remotamente e que era necessário para 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 de 1987-01-27T14: 35: 49). É mais útil na depuração de scripts, pois pode ser conectado a uma linha de comandos para ver quais argumentos são realmente transmitidos 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 "$@"
notações, eis 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
$
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, ' -new
', ' -opt
', ' and
' e ' arg with space
'.
]
Hmm, essa é uma resposta bastante longa - talvez exegese seja o melhor termo. Código-fonte escape
disponível a pedido (email para o ponto de nome sobrenome no gmail ponto 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 aotest.sh
script que Robert Gamble mostrou e pode ser escrito como uma função shell (mas as funções shell não existiam na versão local do Bourne shell quando escrevi pela primeira vezal
).
Observe também que você pode escrever al
como um simples shell script:
[ $# != 0 ] && printf "%s\n" "$@"
O condicional é necessário para que não produza saída quando não são transmitidos argumentos. O printf
comando produzirá uma linha em branco apenas com o argumento de seqüência de formato, mas o programa C não produz nada.