Como colocar uma pesquisa de string com o comando grep na instrução if?

11

Eu quero pesquisar várias seqüências de caracteres em dois arquivos. Se uma string for encontrada nos dois arquivos, faça alguma coisa. Se uma string for encontrada em apenas um arquivo, faça outra coisa.

Meus comandos são os seguintes:

####This is for the affirmative sentence in both files
if grep -qw "$users" "$file1" && grep -qw "$users" "$file2"; then

####This is for the affirmative sentence in only one file, and negative for the other one
if grep -qw "$users" "$file1" ! grep -qw "$users" "$file2"; then

é correto o modo de negar e afirmar as afirmações? Estou usando o shell KSH.

Agradeço antecipadamente.

Mareyes
fonte

Respostas:

11

Tente o seguinte:

if grep -wq -- "$user" "$file1" && grep -wq -- "$user" "$file2" ; then
   echo "string avail in both files"
elif grep -wq -- "$user" "$file1" "$file2"; then
   echo "string avail in only one file"
fi
  • grep pode procurar padrões em vários arquivos, portanto, não é necessário usar um operador OR / NOT.
msp9011
fonte
Existe uma diferença mínima entre o "-wq" e o "-qw" em cada comando?
Mareyes
2
Não, os dois fornecem as opções -q e -w para grep.
Glenn Jackman
3
Por que não há aspas na expansão da variável?
D. Ben Knoble
@ D.BenKnoble está correto: "$ user" "$ file1"?
Mareyes
1
@Mareyes: Sim, isso está correto #
D. Ben Knoble
13

Outra opção:

grep -qw -- "$users" "$file1"; in_file1=$?
grep -qw -- "$users" "$file2"; in_file2=$?

case "${in_file1},${in_file2}" in
    0,0) echo found in both files ;;
    0,*) echo only in file1 ;;
    *,0) echo only in file2 ;;
      *) echo in neither file ;;
esac
Glenn Jackman
fonte
Ei, obrigado pela maneira opcional. Funciona bastante bem !! funciona perfeitamente em outro script que eu tenho.
Mareyes
Essa é uma ótima ideia! Sempre aprendendo.
Joe
7
n=0

#Or if you have more files to check, you can put your while here. 
grep -qw -- "$users" "$file1" && ((n++))
grep -qw -- "$users" "$file2" && ((n++))

case $n in 
   1) 
       echo "Only one file with the string"
    ;;
   2)
       echo "The two files are with the string"
   ;;
   0)
       echo "No one file with the string"
   ;;
   *)
       echo "Strange..."
   ;;
esac 

Nota: ((n++))é uma extensão ksh (também suportada por zshe bash). Na shsintaxe POSIX , você precisaria n=$((n + 1)).

Luciano Andress Martini
fonte
Ah, desculpe, eu estava digitando $ ((n ++)) em vez de ((n ++)). Esqueço.
Luciano Andress Martini
1
Observe que n=$((n++))NUNCA alterará o valor de n porque n++é pós- incremento: retorna o valor atual de n e depois incrementa n; então você define a variável para o valor retornado, que era o n original.
Glenn Jackman
Se você pode assumir que seus nomes de arquivos não contêm novas linhas,, local IFS=$'\n'; matches=( $(grep -lw "$users" "$file1" "$file2") )e use case "${#matches[@]" in. @ Glenn: essa idéia também se aplica à sua resposta, para evitar múltiplas invocações de grep. Encaná-lo grep -l | wc -ltambém funcionaria.
22818 Peter Cordes
@ Peter que valeria a pena escrever como uma resposta separada IMO.
Stephen Kitt
@ StephenKitt: Eu não estava indo, porque não consegui encontrar uma maneira à prova de balas para lidar com caracteres arbitrários de nome de arquivo. Mas como você acha que vale a pena, eu escrevi de qualquer maneira.
Peter Cordes
2

Se os seus nomes de arquivos não contiverem novas linhas, você poderá evitar várias chamadas grepfazendo com que grep imprima os nomes dos arquivos correspondentes e conte os resultados.

 local IFS=$'\n'    # inside a function.  Otherwise use some other way to save/restore IFS
 matches=( $(grep -lw "$users" "$file1" "$file2") )

O número de correspondências é "${#matches[@]}".

Pode haver uma maneira de usar grep --null -lwaqui, mas não sei como analisar a saída . O Bash var=( array elements )não tem como usar um \0delimitador em vez de \n. Talvez o mapfilebuiltin do bash possa fazer isso? Mas provavelmente não, porque você especifica o delimitador com -d string.


Você poderia count=$(grep -l | wc -l), mas então você tem dois processos externos, para que você possa executar apenas grepnos dois arquivos separadamente. (A diferença entre a sobrecarga grepversus a wcinicialização é pequena em comparação com o material fork + exec + linker dinâmico para iniciar um processo separado).

Além disso, wc -lvocê não descobre qual arquivo corresponde.


Com os resultados capturados em uma matriz, isso já pode ser o que você deseja ou, se houver exatamente 1 correspondência, é possível verificar se foi a primeira entrada ou não.

local IFS=$'\n'    # inside a function.  Otherwise use some other way to save/restore IFS
matches=( $(grep -lw "$users" "$file1" "$file2") )

# print the matching filenames
[[ -n $matches ]] && printf  'match in %s\n'  "${matches[@]}"

# figure out which input position the name came from, if there's exactly 1.
if [[ "${#matches[@]" -eq 1 ]]; then
    if [[ $matches == "$file1" ]];then
        echo "match in file1"
    else
        echo "match in file2"
    fi
fi

$matchesé uma abreviação de ${matches[0]}, o primeiro elemento da matriz.

Peter Cordes
fonte
@ StephenKitt: Eu quis dizer -z, não -0, oops. Sim, isso faria o grep imprimir os nomes de arquivos separados por \0como eu já mencionei na resposta, mas como você pode obter o bash para analisar isso? Você não pode definir IFS=$'\0', ou basicamente fazer qualquer outra coisa com find -print0saída de estilo diretamente com o bash.
22418 Peter Cordes
Sim, eu li muito rápido, perdi esse parágrafo e não pensei nas coisas (e foi por isso que excluí meu comentário). Pode haver uma maneira, mas não consigo pensar em uma agora e, como você diz, não vale a pena tornar a solução muito complexa quando há apenas alguns greps para lidar de qualquer maneira.
Stephen Kitt
1
mapfile -d $'\0'funciona, mas substitui as novas linhas por espaços (não tentei ajustar IFS).
Stephen Kitt