Possível em bash? Transformando expansão de comando em argumentos

0

São as únicas opções para expansão do comando bash:

  • sem coquete $(..) cuja saída é sempre analisada com divisão de cadeia burra ou
  • entre aspas "$(..)" cuja saída é sempre passada como uma unidade?

Estou tentando replicar no bash uma função de shell de peixe que criei para uso no Mac OS. Minha função de peixe selection link pega a seleção na janela frontal e gera os caminhos para uso na substituição de comandos como ls -l (selection) . Eu estava esperando conseguir a mesma coisa em bash talvez como ls -l $(selection) .

Eu pensei que era uma questão de citar e tentei passar caminhos delimitados por alimentação de linha para printf "%q " do bash. No entanto, descobri que não importa em qual citação coloquei a saída de substituição de comando, ela estava sendo dividida em espaço em branco.

Exemplo simplificado:

$ touch file\ a file\ b
$ ls $( echo "file\ a file\ b" ) # want expansion equiv to: ls 'file a' 'file b'
ls: a: No such file or directory
ls: b: No such file or directory
ls: file\: No such file or directory
ls: file\: No such file or directory

Não seria o fim do mundo se eu tivesse que usar a substituição de comandos citada como ls -l "$(selection)" , mas fazendo com que a saída do comando nunca fosse dividida, deixa de observar a minha cuidadosa citação. A sintaxe antiga do backtick é diferente?

Engraçado, bash, você tem muitos recursos. Não tem ninguém para permitir cmda $(cmdb-that-generates-parameters-for-cmda) ? Ou um usuário bash simplesmente evita espaços ou símbolos em nomes de arquivos (como um animal) para tornar tudo mais fácil? Obrigado por qualquer resposta.

    
por Pierre Houston 31.08.2018 / 02:05

2 respostas

2

Existem várias maneiras de fazer o que você quer. Por padrão, bash usando espaços em branco como separador padrão, mas você pode facilmente substituir isso usando IFS (Internal Field Separator) ou use técnicas diferentes, como enumeração. Abaixo estão alguns exemplos que primeiro vêm à mente.

Variante # 1

Usando uma variável interna especial IFS (Internal Field Separator)

#!/bin/bash

touch 'file a' 'file b'

# Set IFS to new line as a separator
IFS='
'
ls -la $( echo 'file a'; echo 'file b' )

Variante # 2

Usando for loop

#!/bin/bash

touch 'file a' 'file b'
for variable in 'file a' 'file b';do
  ls -la "${variable}"
done

Variante # 3

Usando for do loop de valores predefinidos

#!/bin/bash

MultiArgs='
arg 0 1
arg 0  2
arg 0   3
arg 0    N
'

# Using only new line as a separator
IFS='
'

for variable in ${MultiArgs};do
  touch "${variable}"
  ls -la "${variable}"
done

Variante # 4

Usando ~ (tilda) como separador

#!/bin/bash

MultiArgs='arg 0 1~arg 0 2~arg0 3~ar g 0 N' # Arguments with spaces

IFS='~'
for file in ${MultiArgs};do
 touch "${file}"
 ls -la "${file}";
done

Is the old backtick syntax any different?

Não, é o mesmo, mas os backticks têm alguma limitação que $() não faz.

Or does a bash user just avoid any spaces or symbols in filenames (like an animal) to make everything easy?

Não, não há problema em usar espaços em nomes de arquivos na medida em que se usaria a cotação correta.

Mais ou menos $(cmdb-that-generates-parameters-for-cmda)

#!/bin/bash

# first command generate text for 'sed' command that replacing . to ###
echo $( for i in {1..5}; do echo "${i} space .";done | sed 's/\./###/g' )

#############################################
# Another example: $() inside of another $()
for i in {1..5}; do touch "${i} x.file";done # Prepare some files with spaces in filenames

IFS='
'
echo "$( ls -la $(for i in ls *.file; do echo "$i"; done))"

Se quiser passar todos os parâmetros em uma única linha para alimentar seu programa transcode , você pode adicionar ao final a seguinte função do arquivo ~/.bashrc :

_tr() {
  local debug
  debug=1
  [ $debug -eq 1 ] && {
    echo "Number of Arguments: $#"
    echo "Arguments: $@"
  }

  transcode "$@"
}

e chame essa função da linha de comando assim:

eval _tr $(echo "$out")

Onde a variável out deve ser assim: out="'file a' 'file b'" .
Se você digitar nomes de arquivos manualmente, a chamada para _tr function será semelhante a:

eval _tr $(echo "'file a' 'file b'")

Se você usasse algum script externo no lugar de $() do que o script / programa externo, deveria retornar a lista de arquivos citados exatamente assim:

"'file a' 'file b'"
    
por 31.08.2018 / 04:03
1

com xargs :

echo '"file a" "file b"' | xargs ls

Cuidado com as citações. Nota xargs não é um Bash construído (estou mencionando isso em um contexto de seu "Bash, você tem muitos recursos").

Com eval , que é um Bash incorporado:

eval ls $( echo '"file a" "file b"' )

Novamente, lembre-se das citações. Isso pode sair pela culatra com facilidade . Métodos baseados em mudar a IFS ( esta outra resposta ) parecem mais seguros. Por outro lado, com eval você pode até fazer isso:

eval ls $( echo '"file "{a,b}' )

Infelizmente, nada disso é tão simples quanto a sintaxe ls (selection) do outro shell com a qual você começou. No entanto, você pode simplificar a sintaxe de qualquer solução (?) Com uma função personalizada, por exemplo:

_(){ "$2" | xargs "$1"; }

Isso permite chamar _ ls selection , se somente selection gerar entrada para xargs . Melhor ainda, isso:

_(){ "${@:$#}" | xargs "${@:1:$(($#-1))}"; }

torna _ ls -l "file c" selection possível. Eu acho que você poderia criar uma função semelhante que usa o método "alterar o IFS " de outra resposta.

Ainda assim, isso não é tão flexível e elegante quanto o ls (selection) do peixe. Espero que você possa fazer ls (selection1) (selection2) em peixes; a função _ acima não cobre este caso.

Por outro lado, eval ls $(selection1) $(selection2) pode funcionar se selection1 e selection2 retornarem cadeias sanitizadas com aspas e / ou saídas adequadas. Se um invasor puder fazer com que você selecione um arquivo chamado ; rm -rf ~/; , é melhor higienizar bem; ou não use eval em primeiro lugar.

    
por 31.08.2018 / 06:36