Часть 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"