Como posso executar um “grep | grep ”comando como uma string em uma função bash?

3

Estou tentando construir um comando que canalize os resultados de um comando grep para outro comando grep em uma função bash. Por fim, quero que o comando seja executado assim:

grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:

A função que estou escrevendo armazena a primeira parte do comando em uma string e, em seguida, acrescenta a segunda parte:

grep_cmd="grep -I -r $pattern $@"

if (( ${#file_types[@]} > 0 )); then
    file_types="${file_types[@]}"
    file_types=.${file_types// /':\|.'}:

    grep_cmd="$grep_cmd | grep $file_types"
fi

echo "$grep_cmd"
${grep_cmd}

Isso gera um erro após a saída da primeira parte:

grep: |: No such file or directory
grep: grep: No such file or directory
grep: .c:\|.h:: No such file or directory

Alterar a última linha de ${grep_cmd} para apenas "$grep_cmd" não exibe saída da primeira parte e gera um erro diferente:

bash: grep -I -r FooBar /code/internal/dev/ /code/public/dev/ | grep .c:\|.h:: No such file or directory

Por esta resposta SO , tentei alterar a última linha para $(grep_cmd) . Isso gera outro erro:

bash: grep_cmd: command not found

Essa resposta de SO sugere o uso de eval $grep_cmd . Isso suprime os erros, mas também suprime a saída.

Este sugere o uso de eval ${grep_cmd} . Isso tem os mesmos resultados (suprime os erros e a saída). Eu tentei ativar a depuração no bash (com set -x ), o que me dá isso:

+ eval grep -I -r FooBar /code/internal/dev/ /code/public/dev/ '|' grep '.c:\|.h:'
++ grep -I -r FooBar /code/internal/dev/ /code/public/dev/
++ grep '.c:|.h:'

Parece que o pipe está sendo escapado, portanto, o shell interpreta o comando como dois comandos. Como faço para escapar corretamente o caractere de pipe para que ele interpreta como um comando?

    
por David Kennedy 08.05.2014 / 19:23

3 respostas

4

Como mencionado no comentário, muita da sua dificuldade é porque você está tentando armazenar um comando em uma variável e depois executar esse comando mais tarde.

Você terá muito mais sorte se simplesmente executar o comando imediatamente em vez de tentar salvá-lo.

Por exemplo, isso deve fazer o que você está tentando realizar:

if (( ${#file_types[@]} > 0 )); then
    regex="${file_types[*]}"
    regex="\.\(${regex// /\|}\):"
    grep -I -r "$pattern" "$@" | grep "$regex"
else
    grep -I -r "$pattern" "$@"
fi
    
por 08.05.2014 / 20:05
1

Uma coisa a ter em mente sobre a programação shell, que muitas vezes não é explicada claramente nos tutoriais, é que existem dois tipos de dados: strings e listas de strings . Uma lista de strings não é a mesma coisa que strings com uma nova linha ou um separador de espaço, é uma coisa própria.

Outra coisa a ter em mente é que a maioria das expansões só é aplicada quando o shell está analisando um arquivo. Executar um comando não envolve expansão.

Algumas expansões acontecem com o valor de uma variável: $foo significa "pegar o valor da variável foo , dividi-lo em uma lista de strings usando whitespace¹ como separadores e interpretar cada elemento da lista como um padrão curinga que é então expandido ”. Essa expansão só acontece se a variável for usada em um contexto que solicita uma lista. Em um contexto que exige uma string, $foo significa "pegue o valor da variável foo ". Aspas duplas impõem um contexto de string, daí o conselho: sempre use substituições de variáveis e substituições de comandos entre aspas duplas: "$foo" , "$(somecommand)" ². (As mesmas expansões acontecem com substituições de comando desprotegidas quanto a variáveis.)

Uma consequência da distinção entre análise e execução é que você não pode simplesmente colocar um comando em uma string e executá-lo. Quando você escreve ${grep_cmd} , apenas a divisão e globbing acontecem, não analisando, então um caractere como | não tem um significado especial.

Se você realmente precisa colocar um comando shell em uma string, você pode eval it:

eval "$grep_cmd"

Observe as aspas duplas - o valor da variável contém um comando shell, então precisamos do valor exato da string. No entanto, essa abordagem tende a ser complicada: você precisa realmente ter algo na sintaxe de origem do shell. Se você precisar, digamos, de um nome de arquivo, esse nome de arquivo deve ser citado corretamente. Então você não pode simplesmente colocar $pattern e $@ lá, você precisa construir uma string que, quando analisada, resulta em uma única palavra contendo o padrão, e em uma lista de palavras contendo os argumentos.

Para resumir: não encha os comandos do shell em variáveis . Em vez disso, use funções . Se você precisar de um comando simples com argumentos e não algo mais complexo, como um pipeline, poderá usar uma matriz (uma variável de matriz armazena uma lista de strings).

Aqui está uma abordagem possível. A função run_grep não é realmente necessária com o código mostrado. Eu estou incluindo aqui, assumindo que esta é uma pequena parte de um script maior e há muito mais código intermediário. Se este for realmente o roteiro inteiro, apenas execute grep no ponto em que você sabe como canalizá-lo. Eu também consertei o código que constrói o filtro, o que não parece certo (por exemplo, . em um regexp significa "qualquer caractere", mas acho que você quer um ponto literal).

grep_cmd=(grep -I -r "$pattern" "$@")

if (( ${#file_types[@]} > 0 )); then
    regexp='\.\('
    for file_type in "${file_types[@]}"; do
      regexp="$regexp$file_type\|"
    done
    regexp="${regexp%?}):"
    run_grep () {
      "${grep_cmd[@]}" | grep "$file_types"
    }
else
  run_grep () {
    "${grep_cmd[@]}"
  }
fi

run_grep

¹ Mais geralmente, usando o valor de IFS .
² Somente para especialistas: sempre use aspas duplas em torno das substituições de variáveis e comandos, a menos que você entenda por que você as está deixando produz o efeito certo.
³ Apenas para especialistas: se você precisar colocar um comando shell em uma variável, tenha muito cuidado com as citações.

Observe que o que você está fazendo parece excessivamente complexo e não confiável - e se você tiver um arquivo que contenha foo.c: 42 ? O GNU grep tem uma opção --include para procurar apenas em determinados arquivos em uma travessia recursiva - apenas use-a.

grep_cmd=(grep -I -r)
for file_type in "${file_types[@]}"; do
  grep_cmd+=(--include "*.$file_type")
done
"${grep_cmd[@]}" "$pattern" "$@"
    
por 09.05.2014 / 03:42
0
command="grep $regex1 filelist | grep $regex2"
echo $command | bash
    
por 22.08.2014 / 21:27