Como é possível executar um comando com um número desconhecido de argumentos em um shell POSIX?

3

Eu quero pesquisar várias palavras dentro do diretório atual. Para conseguir isso eu uso algo assim.

grep -e "word1" -e "word2" -R .

Isso funciona bem.

Levando isso um passo além, criei um arquivo de dicionário que contém todas as palavras em uma nova linha e queria criar o comando grep com esse dicionário.

Por exemplo, eu tenho este arquivo de dicionário

one
two
three
more

e eu quero criar este comando

grep -e "one" -e "two" -e "three" -e "more" -R .

Eu sei que isso pode ser feito com bash e matrizes assim:

pattern=()
while IFS= read -r line; do
    pattern+=("-e" "$line")
done < "$dictionary"

grep "${pattern[@]}" -R .

Mas como isso pode ser feito com um shell somente POSIX?

Eu sei que eu poderia criar uma string que é o comando grep e executá-la com eval , mas como o arquivo do dicionário pode ser qualquer coisa, isso não parece seguro para mim.

  • Existe uma solução sem eval ?
  • Existe uma solução "segura" com eval ?
por Raphael Ahrens 26.09.2014 / 07:57

2 respostas

1

Um shell POSIX possui uma única matriz: os parâmetros posicionais ( $1 , $2 ,…), acessados coletivamente como "$@" e definidos com set -- builtin. Mais precisamente, há uma matriz por instância de função na pilha de chamadas atual, mas somente a matriz de parâmetros posicionais da função atual (ou do script, se estiver fora de qualquer função) está acessível em qualquer ponto. Assim, se você quiser usar esse array, mas não estragar os parâmetros do script, trabalhe em uma função.

Como o sml já observou , seu caso de uso específico com o grep é mais simplesmente resolvido usando a opção -F de grep para fazê-lo ler um padrão por linha de arquivo. Mas aqui está como você pode resolver o mesmo problema para um comando que não tem nada parecido com grep -F .

set -- -a -b
while IFS= read -r line; do
  set -- -e "$line"
done < "$dictionary"
mycommand "$@" more stuff

Isso chama mycommand -a -b line1 line2 … more stuff .

Se você precisar manipular várias listas de nomes de arquivos (ou outras strings com conteúdo arbitrário), você pode fazê-lo com uso criterioso eval e citações muito cuidadosas. Obtendo o cotando duro é complicado. A seguinte função leva dois argumentos: um nome de variável e uma string; ele acrescenta um formulário entre aspas da string à variável. O formulário citado é um literal com aspas simples, com aspas simples apropriadamente com escape e é adequado para análise de shell.

append_to_quoted_list () {
  set -- "$1" "$(printf %s. "$2" | sed "s/'/'\\''/")"
  eval "$1=\"\${$1} '${2%?}'\""
}

Aqui está um exemplo de uso, para construir duas listas ao mesmo tempo. Ele chama $command1 com as linhas passadas como argumentos para -e opções e, em seguida, $command2 com as linhas passadas como --foo=… opções.

list1= list2=
append_to_quoted_list list1 "$command1"
append_to_quoted_list list1 "$command2"
while IFS= read -r line; do
  append_to_quoted_list list1 -e
  append_to_quoted_list list1 "$line"
  append_to_quoted_list list2 "--foo=$line"
done < "$dictionary"
eval "$list1; $list2"
    
por 27.09.2014 / 05:14
3

Com grep você não deseja usar as opções -f ou -F que grep oferece? Na página grep man:

   -F, --fixed-strings
        Interpret PATTERN as a list of fixed strings, separated by 
        newlines, any of which is to be matched.  (-F is specified by 
        POSIX.)

   -f FILE, --file=FILE
        Obtain patterns from FILE, one per line.  The empty file contains 
        zero patterns, and therefore matches nothing.  (-f is specified by
        POSIX.)

Exemplo

$ grep -F somefile.txt -R .
    
por 26.09.2014 / 08:07