Как поместить поиск строки с помощью команды grep в оператор if?

11

Я хочу найти несколько строк в двух файлах. Если одна строка найдена в обоих файлах, то создайте что-нибудь. Если одна строка найдена только в одном файле, создайте другую.

Мои команды следующие:

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

Это правильный способ опровергнуть и подтвердить заявления? Я использую оболочку KSH.

Заранее спасибо.

Mareyes
источник

Ответы:

11

Попробуй это:

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 Можно искать шаблоны в нескольких файлах, поэтому нет необходимости использовать оператор ИЛИ / НЕ.
msp9011
источник
Есть ли минимальная разница между "-wq" и "-qw" в каждой команде?
Mareyes
2
Нет, оба они предоставляют опции -q и -w для grep.
Гленн Джекман
3
Почему нет кавычек на расширение переменной?
Д. Бен Кнобл
@ D.BenKnoble это правильно: "$ user" "$ file1"?
Mareyes
1
@Mareyes да, это правильно
Д. Бен Нобл
13

Другой вариант:

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
Гленн Джекман
источник
Эй, спасибо за дополнительный путь. Это работает достаточно хорошо! отлично работает в другой скрипт, который у меня есть.
Mareyes
Это отличная идея! Всегда учусь.
Джо
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 

Примечание: ((n++))это расширение ksh (также поддерживается zshи bash). В shсинтаксисе POSIX , вам нужно n=$((n + 1))вместо этого.

Лучано Андресс Мартини
источник
Извините, я печатал $ ((n ++)) вместо ((n ++)). Забудьте.
Лучано Андресс Мартини
1
Следует отметить , что n=$((n++))никогда не будет изменять значение п потому , что n++это сообщение приращение: она возвращает текущее значение п, то приращение п; затем вы устанавливаете переменную на возвращаемое значение, которое было исходным n.
Гленн Джекман
Если вы можете предположить, что ваши имена файлов не содержат новых строк local IFS=$'\n'; matches=( $(grep -lw "$users" "$file1" "$file2") ), и используйте case "${#matches[@]" in. @glenn: эта идея относится и к вашему ответу, чтобы избежать многократных вызовов grep. Трубопровод это grep -l | wc -lтакже будет работать.
Питер Кордес
@ Питер, который стоит написать в качестве отдельного ответа IMO.
Стивен Китт
@StephenKitt: я не собирался, потому что я не мог найти пуленепробиваемый способ обработки произвольных символов имени файла. Но так как вы думаете, что это того стоит, я все равно это написал.
Питер Кордес
2

Если ваши имена файлов не содержат символов новой строки, вы можете избежать нескольких вызовов grep, если grep напечатает имена подходящих файлов и посчитает результаты.

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

Количество матчей "${#matches[@]}".

Может быть, есть способ использовать grep --null -lwздесь, но я не уверен, как проанализировать вывод . У Bash var=( array elements )нет способа использовать \0разделитель вместо \n. Может быть, mapfileвстроенный Bash может сделать это? Но, вероятно, нет, потому что вы указываете разделитель с -d string.


Вы могли бы count=$(grep -l | wc -l), но тогда у вас есть два внешних процесса, так что вы могли бы просто запустить grepдва файла по отдельности. (Разница между grepпротив wcнакладных расходов запуска мала по сравнению с вилочным + + Exec динамический компоновщик материал , чтобы запустить отдельный процесс вообще).

Кроме того, с wc -lвами не узнать, какой файл соответствует.


С результатами, захваченными в массиве, это, возможно, уже то, что вы хотите, или если есть ровно 1 совпадение, вы можете проверить, был ли это первый ввод или нет.

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является сокращением для ${matches[0]}первого элемента массива.

Питер Кордес
источник
@StephenKitt: Я имел в виду -z, нет -0, упс. Да, это заставит grep распечатать имена файлов, разделенные так, \0как я уже упоминал в ответе, но как вы можете получить bash для этого? Вы не можете устанавливать IFS=$'\0'или вообще делать что-либо еще с find -print0выводом стиля напрямую с помощью bash.
Питер Кордес
Да, я слишком быстро прочитал, пропустил этот абзац и не продумывал все (поэтому я удалил свой комментарий). Вполне возможно, что есть способ, но я не могу придумать один из них сейчас, и, как вы говорите, не стоит делать решение слишком сложным, когда grepв любом случае есть несколько вариантов, с которыми приходится иметь дело.
Стивен Китт
1
mapfile -d $'\0'вроде работает, но заменяет символы новой строки пробелами (я не пробовал настраивать IFS).
Стивен Китт