bash sed не работает в то время как цикл - PullRequest
0 голосов
/ 28 декабря 2011
#!/bin/bash
fname=$2
rname=$1
echo "$(<$fname)" | while read line ; do
    result=`echo "$(<$rname)" | grep "$line"; echo $?`
    if [ $result != 0 ]
    then
        sed  '/$line/d' $fname > newkas
    fi 2> /dev/null
done

Привет всем, я новичок в bash.

У меня есть два списка, один старше другого.Я хочу сравнить имена на «fname» с «rname».«Результат» - это стандартная выходная информация, которую я получу, если имя все еще доступно в «rname».если нет, то я получу ненулевой вывод.Использование sed для удаления этой строки и перенаправления ее в новый файл.

Я пробовал часть кода, и он работает, пока я не добавлю функцию while.Кажется, что sed не работает, так как окончательный вывод newkas такой же, как и исходный ввод fname.Мой метод неверен или я пропустил какие-либо детали?

Ответы [ 2 ]

6 голосов
/ 28 декабря 2011

Часть 1: Что не так

Причина, по которой ваше sed выражение "не работает", заключается в том, что вы использовали одинарные кавычки. Вы сказали

sed  '/$line/d' $fname > newkas

Предположим, fname=input.txt' и line='example text' это расширится до:

sed  '/$line/d' input.txt > newkas

Обратите внимание, что $line все еще присутствует буквально. Это связано с тем, что bash не будет интерполировать переменные внутри одинарных кавычек, поэтому sed видит $ буквально.

Вы можете исправить это, сказав

sed  "/$line/d/" $fname > newkas

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

sed '/'"$line"'/d/' $fname > newkas

Который немного сложнее для чтения, но, если вы посмотрите внимательно, заключите в кавычки все, что я собираюсь сделать частью выражения sed, и двойные кавычки в переменную, которую я хочу раскрыть.

Часть 2. Как его улучшить

Ваш скрипт содержит ряд вещей, которые можно улучшить.

echo "$(<$fname)" | while read line ; do
    :
done

Во-первых, вы читаете файл с помощью "$(<$fname)", когда вы можете просто перенаправить стандартный цикл while. Это немного избыточно, но, что более важно, вы используете while, что создает дополнительный подоболочек и означает, что вы не можете изменять какие-либо переменные из окружающей области. Проще сказать

while IFS= read -r line ; do
    :
done < "$fname"

Далее рассмотрим ваш grep

echo "$(<$rname)" | grep "$line"

Опять вы читаете файл и выводите его в grep. Но grep может читать файлы напрямую.

grep "$line" "$rname"

После этого вы выводите код возврата и проверяете его значение в операторе if, который является классической бесполезной конструкцией .

result=$( grep "$line" "$rname" ; echo $?)

Вместо этого вы можете просто передать grep непосредственно в if, что проверит его код возврата.

if grep -q "$line" "$rname" ; then
    sed  "/$line/d" "$fname" > newkas
fi

Обратите внимание, что я процитировал $fname, что важно, если в нем может быть пробел. Я также добавил -q к grep, что подавляет его вывод.

Теперь нет необходимости подавлять сообщения об ошибках из оператора if, потому что нам не нужно беспокоиться о $result, содержащем необычное значение, или grep, не возвращающемся должным образом.

Окончательный результат - скрипт

while IFS= read -r line ; do
    if grep -q "$line" "$rname" ; then
        sed  "/$line/d" "$fname" > newkas
    fi
done < "$fname"

Что не будет работать, потому что newkas перезаписывается в каждом цикле. Это означает, что в конце была использована только последняя строка в $fname. Вместо этого вы могли бы сказать:

cp "$fname" newkas
while IFS= read -r line ; do
    if grep -q "$line" "$rname" ; then
        sed  -i '' "/$line/d" newkas
    fi
done < "$fname"

Что, я полагаю, сделает то, что вы ожидаете.

Часть 3: Но не делайте этого

Но это все имеет отношение к решению вашей актуальной проблемы. Мне кажется, что вы хотите просто создать файл newkas, который содержит все строки $fname, за исключением тех, которые появляются в $rname. Это легко сделать с помощью утилиты comm:

comm -2 -3 <(sort "$fname") <(sort "$rname") > newkas

Это также меняет порядок сортировки строк, что может быть не очень хорошо для вас. Если вы хотите сделать это без изменения порядка, лучше использовать метод @fge.

grep -F -v -x -f "$rname" "$fname"
2 голосов
/ 28 декабря 2011

Если я правильно понимаю вашу потребность, вам нужен файл newaks, который содержит строки в $fname, которые также находятся в $rname.

Если это то, что вы хотите, использование sed - это излишне. Используйте fgrep:

fgrep -x -f $fname $rname > newkas

Также есть проблемы с вашим скриптом:

  • вы фиксируете вывод grep в result, что означает, что он никогда не будет точно равен 0; вам нужно выполнить команду и просто проверить $?
  • ваши echo запутаны, просто сделайте grep whatever thefilename или while...done <thefile;
  • наконец, вы берете строку как есть из исходного файла: строка может быть регулярным выражением, что означает, что вы попытаетесь сопоставить регулярное выражение в $rname, что может привести к неожиданным результатам.

и др.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...