Я пытаюсь проанализировать каждую строку файла и найти определенную строку. Скрипт, похоже, выполняет свою работу, однако параллельно он пытается выполнить команду if в строке 6:

#!/bin/bash
for line in $(cat $1)
do
echo $line | grep -e "Oct/2015"
  if($?==0); then
  echo "current line is: $line"
  fi
done

И я получаю следующее (мой сценарий - readlines.sh)

./readlines.sh: line 6: 0==0: command not found
0
Hadi 16 Окт 2015 в 19:18

4 ответа

Лучший ответ

Во-первых: как говорит мистер Лама, вам нужно больше места. Прямо сейчас ваш сценарий пытается найти для запуска файл с именем вроде /usr/bin/0==0. Вместо:

[ "$?" -eq 0 ] # POSIX-compliant numeric comparison
[ "$?" = 0 ]   # POSIX-compliant string comparison
(( $? == 0 ))  # bash-extended numeric comparison

Во-вторых: в этом случае вообще не тестируйте $?. Фактически, у вас даже нет веской причины использовать grep; следующее является более эффективным (поскольку оно использует только встроенные в bash функциональные возможности и не требует вызова внешних команд) и более читабельным:

if [[ $line = *"Oct/2015"* ]]; then
  echo "Current line is: $line"
fi

Если вам действительно действительно нужно использовать grep, напишите это так:

if echo "$line" | grep -q "Oct/2015"; then
  echo "Current line is: $line"
fi

Таким образом, if работает непосредственно с статусом выхода конвейера, а не запускает вторую команду тестирования $? и работает со статусом выхода этой команды.

2
Charles Duffy 16 Окт 2015 в 16:48

Интервал важен при написании сценариев оболочки.
Кроме того, двойные скобки предназначены для числового сравнения, а не одинарные.

if (( $? == 0 )); then
0
Mr. Llama 16 Окт 2015 в 16:20

Если вам нравятся однострочные символы, вы можете использовать оператор И (&&), например:

echo "$line" | grep -e "Oct/2015" && echo "current line is: $line"

Или же:

grep -qe "Oct/2015" <<<"$line" && echo "current line is: $line"
1
kenorb 16 Окт 2015 в 19:13

У @Charles Duffy есть хороший ответ, который я поддержал как правильный (и это так), но вот подробное, построчное разбиение вашего скрипта и правильные действия для каждой его части.

for line in $(cat $1)

Как я отмечал в своем комментарии в другом месте, это должно быть сделано как конструкция while read, а не как конструкция for cat.

  • Эта конструкция будет разделять каждую строку на слова, делая пробелы в файле отдельными «строками» в выводе.
  • Все пустые строки будут пропущены.

Кроме того, когда вы указываете $1, переменная должна быть заключена в кавычки. Если это не кавычки, пробелы и другие менее обычные символы, появляющиеся в имени файла, вызовут сбой кошки, и цикл не будет обрабатывать файл.

Полная строка будет гласить:

while IFS= read -r line

Наглядный пример компромиссов можно найти здесь. Связанный тестовый сценарий следует. Я попытался указать, почему важны IFS= и -r.

#!/bin/bash

mkdir -p /tmp/testcase
pushd /tmp/testcase >/dev/null

printf '%s\n' '' two 'three three' '' '    five with leading spaces' 'c:\some\dos\path' '' > testfile

printf '\nwc -l testfile:\n'

wc -l testfile

printf '\n\nfor line in $(cat) ... \n\n'

let n=1
for line in $(cat testfile) ; do
    echo line $n: "$line"
    let n++
done

printf '\n\nfor line in "$(cat)" ... \n\n'

let n=1
for line in "$(cat testfile)" ; do
    echo line $n: "$line"
    let n++
done

let n=1
printf '\n\nwhile read ... \n\n'

while read line ; do
    echo line $n: "$line"
    let n++
done < testfile

printf '\n\nwhile IFS= read ... \n\n'

let n=1
while IFS= read line ; do
    echo line $n: "$line"
    let n++
done < testfile

printf '\n\nwhile IFS= read -r ... \n\n'

let n=1
while IFS= read -r line ; do
    echo line $n: "$line"
    let n++
done < testfile

rm -- testfile

popd >/dev/null

rmdir /tmp/testcase

Обратите внимание, что это тяжелый пример с использованием bash. Другие оболочки, например, не поддерживают -r для чтения и не переносят let. Переходим к следующей строке вашего скрипта.

do

Что касается стиля, я предпочитаю использовать в той же строке, что и объявление for или while, но по этому поводу нет соглашения.

echo $line | grep -e "Oct/2015"

Здесь следует указать переменную $line. В общем, имея в виду всегда, если вы специально не знаете , вы должны заключать все раскрытия в двойные кавычки - и это означает как подоболочки, так и переменные. Это изолирует вас от самых неожиданных странностей оболочки.

Вы объявили свою оболочку как bash, что означает, что вам будет доступен оператор «Здесь, строка» <<<. Когда доступно, его можно использовать, чтобы избежать трубы; каждый элемент конвейера выполняется в подоболочке, что влечет за собой дополнительные накладные расходы и может привести к неожиданному поведению, если вы попытаетесь изменить переменные. Это было бы записано как

grep -e "Oct/2015" <<<"$line"

Обратите внимание, что я процитировал расширение line.

Вы вызвали grep с -e, что не является неправильным, но излишне, поскольку ваш шаблон не начинается с -. Кроме того, вы заключили строку в кавычки в оболочке, но вы не пытаетесь расширить переменную или использовать внутри нее другую интерполяцию оболочки. Если вы не ожидаете и не хотите, чтобы содержимое строки в кавычках рассматривалось оболочкой как особенное, вы должны заключить их в одинарные кавычки. Кроме того, использование вами grep неэффективно: поскольку ваш шаблон является фиксированной строкой, а не регулярным выражением, вы могли бы использовать fgrep или grep -F, которые содержат строку, а не соответствие регулярному выражению (и из-за этого намного быстрее). Так это могло быть

grep -F 'Oct/2015' <<<"$line"

Без изменения поведения.

if($?==0); then

Это источник вашей первоначальной проблемы. В сценариях оболочки команды разделяются пробелами; когда вы говорите if($?==0), $? расширяется, вероятно, до 0, и bash попытается выполнить команду под названием if(0==0), которая является допустимым именем команды. Что вы хотели сделать, так это вызвать команду if и указать ей некоторые параметры, что требует большего количества пробелов. Я считаю, что другие достаточно подробно осветили это.

Вам никогда не нужно проверять значение $? в сценарии оболочки. Команда if существует для поведения ветвления на основе кода возврата любой команды, которую вы ей передаете, поэтому вы можете встроить свой вызов grep и напрямую проверить код возврата if, таким образом:

if grep -F 'Oct/2015` <<<"$line" ; then

Обратите внимание на большие пробелы вокруг разделителя ;. Я делаю это потому, что пробелы в оболочке обычно требуются и могут быть опущены только некоторые элементы. Вместо того, чтобы пытаться вспомнить, когда вы можете это сделать, я рекомендую дополнительное пространство вокруг всего. В этом нет ничего плохого, и другие ошибки легче заметить.

Как уже отмечали другие, grep будет печатать совпадающие строки в стандартный вывод, что, вероятно, вам не нужно. Если вы используете GNU grep, который является стандартным для Linux, вам будет доступен переключатель -q. Это подавит вывод из grep

if grep -q -F 'Oct/2015' <<<"$line" ; then

Если вы пытаетесь строго соответствовать стандартам или находитесь в среде с grep, которая не знает -q, стандартный способ добиться этого эффекта - перенаправить stdout на /dev/null/

if printf "$line" | grep -F 'Oct/2015' >/dev/null ; then

В этом примере я также удалил строковое bashism здесь, чтобы показать переносную версию этой строки.

echo "current line is: $line"

В этой строке вашего скрипта нет ничего плохого, за исключением того, что хотя echo - стандартные реализации, различаются до такой степени, что невозможно полностью полагаться на его поведение. Вы можете использовать printf везде, где хотите использовать echo, и вы можете быть уверены в том, что он напечатает. Даже printf имеет некоторые предостережения: некоторые необычные escape-последовательности не поддерживаются равномерно. Подробнее см. mascheck.

printf 'current line is: %s\n' "$line"

Обратите внимание на явную новую строку в конце; printf не добавляет его автоматически.

    fi

Без комментариев к этой строке.

done

В случае, если вы сделали, как я рекомендовал, и заменили строку for конструкцией while read, эта строка изменится на:

done < "$1"

Это направляет содержимое файла в переменной $1 на стандартный ввод цикла while, который, в свою очередь, передает данные в read.

Для ясности я рекомендую сначала скопировать значение из $1 в другую переменную. Таким образом, когда вы читаете эту строку, цель становится более ясной.

Я надеюсь, что никто не обидится на сделанный выше стилистический выбор, который я попытался отметить; есть много способов сделать это (но не очень много правильных ) способов.

Обязательно всегда запускайте интересные фрагменты с помощью отличного shellcheck и объяснить оболочку, когда вы столкнетесь с подобными трудностями в будущем.

И, наконец, вот все вместе:

#!/bin/bash
input_file="$1"
while IFS= read -r line ; do
    if grep -q -F 'Oct/2015' <<<"$line" ; then
        printf 'current line is %s\n' "$line"
    fi
done < "$input_file"
2
sorpigal 16 Окт 2015 в 23:22