Parâmetros da Função Bash - Espaços no Nome do Arquivo [duplicado]

1

Eu sei que existem regras para citar parâmetros, mas não importa o que eu tente, não consigo descobrir como fazer isso funcionar.

Eu tenho uma função que usa um único nome de arquivo como parâmetro, então usa esse nome para construir uma linha de comando e executa a linha de comando. Eu sou capaz de fazê-lo funcionar quando não há espaços em nenhum dos arquivos, mas quando um nome de arquivo com um espaço é introduzido, ele quebra. Tudo o que tento fazer com que funcione com espaços ou não funciona ou faz com que ele não funcione nos arquivos sem espaços também.

Aqui está um script de exemplo que funciona sem espaços:

#!/bin/bash
list_files() {
    file="${1}"
    cmd="cat ${file}"
    echo "Running command: '${cmd}'"
    ${cmd}
}

TARGET_DIR="${1}"

while IFS= read -d $'
./files/
|-- one                # ==> Contains the text "files/one"
|-- two                # ==> Contains the text "files/two"
|-- three and four     # ==> Contains the text "files/three and four"
|-- five               # ==> Contains the text "files/five"
' -r file; do list_files "${file}" done < <(find ${TARET_DIR} -type f -print 0)

Acabei de criar uma estrutura de diretórios da seguinte forma:

bash-3.2$ ./test.bash ./files
Running Command: 'cat ./files/one'
files/one
Running Command: 'cat ./files/two'
files/two
Running Command: 'cat ./files/three and four'
cat: ./files/three: No such file or directory
cat: and: No such file or directory
cat: four: No such file or directory
Running Command: 'cat ./files/five'
files/five

Quando executo o script acima no diretório acima, recebo a seguinte saída:

#!/bin/bash
list_files() {
    file="${1}"
    cmd="cat ${file}"
    echo "Running command: '${cmd}'"
    ${cmd}
}

TARGET_DIR="${1}"

while IFS= read -d $'
./files/
|-- one                # ==> Contains the text "files/one"
|-- two                # ==> Contains the text "files/two"
|-- three and four     # ==> Contains the text "files/three and four"
|-- five               # ==> Contains the text "files/five"
' -r file; do list_files "${file}" done < <(find ${TARET_DIR} -type f -print 0)

O que estou fazendo de errado e como posso criar um script como esse, não importa quais sejam os nomes dos arquivos?

NOTA: Para este exemplo seria simples o suficiente para não armazenar o comando em uma variável primeiro e apenas imprimi-lo, em seguida, chamá-lo digitando o comando em si duas vezes, mas para o meu script real, o comando é muito mais complexo e eu gostaria de ter apenas que mantê-lo em um único lugar, caso precise mudar no futuro.

    
por Kris 03.06.2016 / 06:13

1 resposta

2

Em vez de:

cmd="cat ${file}"
printf '%s\n' "Running command: '${cmd}'"
${cmd}

Transmita um comando fixo para eval para que ele seja interpretado como código de shell.

cmd='cat -- "$file"'
printf '%s\n' "Running command: '${cmd}'"
eval "$cmd"

Isso gerará Running command: cat -- "$file" , o que provavelmente não é o que você deseja.

Ou ( bash specific) use printf %q para citar $file corretamente para que sua expansão possa ser passada para eval :

printf -v cmd 'cat -- %q' "$file"
printf '%s\n' "Running command: $cmd"
eval "$cmd"

Ou, como é um comando simples aqui, você pode armazenar os argumentos desse simples comando em uma matriz:

cmd=(cat -- "$file")
printf 'Running command: '
printf ' %q' "${cmd[@]}"
printf '\n'
"${cmd[@]}"

Se você souber de um caractere em particular que $file tem a garantia de não conter : (note que : é um caractere perfeitamente válido em um nome de arquivo no Unix), você poderia fazer:

cmd="cat:--:$file"
IFS=:  # split on :
set -f # disable glob
# use the split+glob operator by leaving the variable unquoted:
printf 'Running command: '
printf ' %q' $cmd
printf '\n'
$cmd

Agora, neste caso específico, você também pode fazer:

(PS4='Running command: '
set -x; cat -- "$file")

(cuidado, a mensagem vai para stderr).

Ou até mesmo:

PS4='Running command: ' find "$TARGET_DIR" -type f -exec sh -xc '
  cat "$1"' sh {} \;

Ou para evitar a execução de muitos comandos sh e cat , use ksh93 onde cat é um mapeado para / opt / ast / bin / cat e use a sintaxe {} + para executar o mínimo conchas possíveis:

PS4='Running command: ' find "$TARGET_DIR" -type f -exec ksh93 -c '
  PATH=/opt/ast/bin; set -x; for file do cat "$file"; done' ksh93 {} +

Para exibir o conteúdo de arquivos com cabeçalhos e executar o menor número de comandos possível, nos sistemas GNU, você também pode usar head :

$ find "$TARGET_DIR" -type f -exec head -vc-0 {} +
==> ./file 1 <==
1
2

==> ./file 2 <==
test

com zsh :

zmodload zsh/mapfile
for file (**/*(D.)) printf '==> %q <==\n%s\n' $file $mapfile[$file]
    
por 03.06.2016 / 07:45