Jak umieścić wyszukiwanie ciągu za pomocą polecenia grep w instrukcji if?

11

Chcę wyszukać wiele ciągów w dwóch plikach. Jeśli w obu plikach zostanie znaleziony jeden ciąg, zrób coś. Jeśli jeden ciąg znajduje się tylko w jednym pliku, zrób inną rzecz.

Moje polecenia są następne:

####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

czy to poprawny sposób na zaprzeczenie i potwierdzenie oświadczeń? pd Używam powłoki KSH.

Z góry dziękuję.

Mareyes
źródło

Odpowiedzi:

11

Spróbuj tego:

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 może wyszukiwać wzorce w wielu plikach, więc nie trzeba używać operatora OR / NOT.
msp9011
źródło
Czy istnieje minimalna różnica między „-wq” a „-qw” w każdym poleceniu?
Mareyes,
2
Nie, oba zapewniają opcje -q i -w dla grep.
glenn jackman
3
Dlaczego nie ma cytatów na temat rozszerzenia zmiennej?
D. Ben Knoble,
@ D.BenKnoble to poprawnie: „$ user” „$ file1”?
Mareyes,
1
@Mareyes tak, to prawda
D. Ben Knoble
13

Inna opcja:

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
źródło
Hej, dziękuję za opcjonalny sposób. Działa wystarczająco dobrze !! działa idealnie w innym skrypcie, który mam.
Mareyes
To świetny pomysł! Zawsze się uczę.
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 

Uwaga: ((n++))to rozszerzenie ksh (obsługiwane również przez zshi bash). W shskładni POSIX potrzebujesz n=$((n + 1))zamiast tego.

Luciano Andress Martini
źródło
Och, przepraszam, że wpisałem $ ((n ++)) zamiast ((n ++)). Zapomnieć.
Luciano Andress Martini
1
Należy pamiętać, że n=$((n++))nigdy się nie zmienią wartość n, ponieważ n++jest po inkrementacji: zwraca aktualną wartość n, wtedy przyrosty n; następnie ustaw zmienną na zwróconą wartość, która była oryginalnym n.
glenn jackman
Jeśli możesz założyć, że twoje nazwy plików nie zawierają nowych linii local IFS=$'\n'; matches=( $(grep -lw "$users" "$file1" "$file2") ), i użyj case "${#matches[@]" in. @glenn: ten pomysł dotyczy również twojej odpowiedzi, aby uniknąć wielokrotnego wywoływania grep. Pipingowanie go grep -l | wc -lrównież działałoby.
Peter Cordes
@Peter, który warto zapisać jako osobną odpowiedź IMO.
Stephen Kitt
@StephenKitt: Nie zamierzałem tego robić, ponieważ nie mogłem znaleźć kuloodpornego sposobu na obsługę dowolnych znaków w nazwach plików. Ale skoro uważasz, że warto, i tak to napisałem.
Peter Cordes,
2

Jeśli twoje nazwy plików nie zawierają nowych linii, możesz uniknąć wielokrotnych wywołań grep, drukując w grep nazwy pasujących plików i policz wyniki.

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

Liczba dopasowań to "${#matches[@]}".

Może być tutaj sposób na użycie grep --null -lw, ale nie jestem pewien, jak przeanalizować dane wyjściowe . Bash var=( array elements )nie ma sposobu używania \0ogranicznika zamiast \n. Może mapfilewbudowane bash może to zrobić? Ale prawdopodobnie nie, ponieważ określasz separator za pomocą -d string.


Możesz count=$(grep -l | wc -l), ale wtedy masz dwa procesy zewnętrzne, więc równie dobrze możesz po prostu uruchomić grepdwa pliki osobno. (Różnica między narzutem początkowym grepa wcpoczątkowym jest niewielka w porównaniu do opcji fork + exec + linker dynamiczny, aby w ogóle uruchomić osobny proces).

Poza wc -ltym nie dowiesz się, który plik pasuje.


Po przechwyceniu wyników w tablicy może to być już to, czego chcesz, lub jeśli istnieje dokładnie 1 dopasowanie, możesz sprawdzić, czy to było pierwsze wejście, czy nie.

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

$matchesjest skrótem ${matches[0]}od pierwszego elementu tablicy.

Peter Cordes
źródło
@StephenKitt: miałem na myśli -z, nie -0, ups. Tak, to spowodowałoby, że grep wypisałby nazwy plików rozdzielone przez, \0tak jak już wspomniałem w odpowiedzi, ale jak możesz dostać bash, aby to przeanalizować? Nie możesz ustawiać IFS=$'\0', ani robić nic innego z find -print0wyjściem stylu bezpośrednio za pomocą bash.
Peter Cordes
Tak, czytam zbyt szybko, brakuje mi tego akapitu i nie zastanawiałem się (dlatego usunąłem swój komentarz). Być może jest jakiś sposób, ale nie mogę teraz wymyślić żadnego z nich, a jak mówisz, nie warto komplikować rozwiązania zbyt skomplikowanym, skoro i tak jest tylko kilka grep.
Stephen Kitt
1
mapfile -d $'\0'trochę działa, ale zastępuje nowe wiersze spacjami (nie próbowałem poprawiać IFS).
Stephen Kitt