Eu tenho um comando complexo que eu gostaria de criar um script shell / bash. Eu posso escrever em termos de $1
facilidade:
foo $1 args -o $1.ext
Quero poder passar vários nomes de entrada para o script. Qual é o caminho certo para fazer isso?
E, é claro, quero lidar com nomes de arquivos com espaços neles.
bash
command-line
Thelema
fonte
fonte
Respostas:
Use
"$@"
para representar todos os argumentos:Isso irá percorrer cada argumento e imprimi-lo em uma linha separada. $ @ se comporta como $ *, exceto que, quando citados, os argumentos são divididos corretamente se houver espaços neles:
fonte
"$@"
disso é que ele se expande para nada quando não há parâmetros posicionais, enquanto se"$*"
expande para a string vazia - e sim, há uma diferença entre nenhum argumento e um argumento vazio. Veja${1:+"$@"} in
/ bin / sh` .$var
eu era capaz de executar os argumentos.shift
joga fora$1
e desloca todos os elementos subsequentes.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:
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 usareval
: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).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 isso
escape
(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 umdelta
(pense no check-in ) e umget
(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.(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 queecho
faz, mas garante que os argumentos sejam protegidos para uso comeval
(um nível deeval
; eu tenho um programa que executou a shell remotamente e que era necessário para escapar da saída deescape
).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.[ Adicionado: E agora, para mostrar a diferença entre as várias
"$@"
notações, eis mais um exemplo: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: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 paraal
é incrivelmente simples:Isso é tudo. É equivalente ao
test.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: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.fonte
Observe que a resposta de Robert está correta e também funciona
sh
. Você pode (portably) simplificá-lo ainda mais:é equivalente a:
Ou seja, você não precisa de nada!
Teste (
$
é o prompt de comando):Eu li pela primeira vez sobre isso no Unix Programming Environment por Kernighan e Pike.
Em
bash
,help for
documenta isso:fonte
"$@"
significa o enigmático e, depois que você souber o quefor i
significa, não é menos legível do quefor i in "$@"
.for i
é melhor porque salva as teclas digitadas. Eu comparei a legibilidade defor i
efor i in "$@"
.Para casos simples, você também pode usar
shift
. Trata a lista de argumentos como uma fila. Cada umshift
joga fora o primeiro argumento e o índice de cada um dos argumentos restantes é diminuído.fonte
shift
um loop for, esse elemento ainda faz parte da matriz, enquanto que com issoshift
funciona conforme o esperado.echo "$1"
para preservar o espaçamento no valor da sequência de argumentos. Além disso, (uma questão menor) isso é destrutivo: você não pode reutilizar os argumentos se tiver mudado todos eles. Muitas vezes, isso não é um problema - você só processa a lista de argumentos uma vez.--file myfile.txt
Então, US $ 1 é o parâmetro, $ 2 é o valor e você chamar Shift duas vezes quando você precisa pular outro argumentoVocê também pode acessá-los como elementos de uma matriz, por exemplo, se você não deseja iterar por todos eles
fonte
./my-script.sh param1 "param2 is a quoted param"
: - usando argv = ($ @) você obterá [param1, param2], - usando argv = ("$ @"), você obterá [param1, param2 é um parâmetro citado]fonte
[ $# != 0 ]
é melhor. No exemplo acima,#$
deve ser$#
como nawhile [[ $# > 0 ]] ...
Ampliando a resposta do baz, se você precisar enumerar a lista de argumentos com um índice (como procurar uma palavra específica), poderá fazer isso sem copiar a lista ou alterá-la.
Digamos que você queira dividir uma lista de argumentos em um traço duplo ("-") e passar os argumentos antes dos traços para um comando e os argumentos após os traços para outro:
Os resultados devem ficar assim:
fonte
getopt Use o comando em seus scripts para formatar qualquer opção ou parâmetro de linha de comando.
fonte