Bash при чтении строки цикл не выводит каждую строку в состоянии

#bash #awk

#bash #awk

Вопрос:

У меня следующая ситуация:

У меня есть текстовый файл, который я пытаюсь выполнить в цикле, чтобы я мог знать, совпадает ли каждая строка с «.mp3», в данном случае которая является этой:

 12 Stones.mp3
randomfile.txt
Aclarion.mp3
ransomwebpage.html
Agents Of The Sun.mp3
randomvideo.mp4
  

Итак, я написал следующий скрипт для его обработки:

 while read line || [ -n "$line" ]
do
varline=$(awk '/.mp3/{print "yes";next}{print "no"}')
echo $varline
if [ "$varline" == "yes" ]; then
        some-command
    else
         some-command
    fi
done < file.txt
  

Ожидаемый результат будет:

 yes
no
yes
no
yes
no
  

Вместо этого, кажется, пропускает первую строку, и я получаю следующее:

 no
yes
no
yes
no
  

Комментарии:

1. Ваша awk команда использует весь ввод после первого вызова read ; ваша while выполняется только один раз.

Ответ №1:

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

 while IFS= read -r line; do
    case $line in
     *.mp3) some-command;,
     *) some-other-command;;
    esac
done <file.txt
  

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

 awk '{ print ($0 ~ /.mp3$/) ? "yes" : no" }' file.txt |
while IFS= read -r whether; do
    case $whether in
     'yes') some-command ;;
     'no') some-other-command;;
    esac
done
  

Если вам также нужно содержимое "$line" , печать этого также из Awk и чтение двух разных переменных является тривиальным изменением.

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

Комментарии:

1. Обратите также внимание на исправленное регулярное выражение с обратной косой чертой перед точкой и $ привязкой к концу строки.

2. Хотя я, очевидно, согласен с looping the lines in a shell loop is inefficient and very often an antipattern в целом, в этом случае часть предварительной обработки awk не исключает цикл оболочки и оператор case для каждой строки, поэтому IMHO бесполезно. Не забудьте использовать while IFS= по умолчанию для циклов чтения.

3. @EdMorton Спасибо; обновлено. Третьим вариантом было бы выполнение команд с system() из скрипта Awk, но это еще одна банка с червями, которую я не хотел открывать.

Ответ №2:

Используйте awk

 $ awk '{if ($0 ~ /mp3/) {print "yes"} else {print "no"}}' file.txt
yes
no
yes
no
yes
no
  

Или более лаконичный:

 $ awk '/mp3/{print "yes";next}{print "no"}' file.txt
$ awk '{print (/mp3/ ? "yes" : "no")}' file.txt
  

Комментарии:

1. Что произойдет с файлом с именем mp3_list.html ? Возможно, было бы разумно закрепить регулярное выражение в конце и проверить наличие явного расширения, например ($0 ~ /[.]mp3$/) .

Ответ №3:

Вы что-то забыли? У вашего awk нет явного ввода, измените на это вместо этого:

 while IFS= read -r read line || [ -n "$line" ]
do
varline=$(echo "$line" | awk '/.mp3/{print "yes";next}{print "no"}')
echo $varline
if [ "$varline" == "yes" ]; then
        some-command
    else
        some-other-command
    fi
done < file.txt
  

В этом случае вам может потребоваться изменить на /.mp3$/ или /.mp3[[:space:]]*$/ для точного соответствия.
Потому что . будет соответствовать любому символу, так что, например, /.mp3/ тоже будет соответствовать Exmp3but.mp4 .
Обновление: изменено while read line на while IFS= read -r read line , чтобы сохранить содержимое каждой строки нетронутым при присвоении переменной.

И awk часть может быть улучшена до:

 awk '{print $0~/.mp3$/ ? "yes":"no"}'
  

Итак, с awk only вы можете сделать это следующим образом:

 awk '{print $0~/.mp3$/ ? "yes":"no"}' file.txt
  

Или, если ваша цель — просто команды в структуре if, вы можете просто сделать это:

 awk '/.mp3$/{system("some-command");next}{system("some-other-command");}' file.txt
  

или это:

 awk '{system($0~/.mp3$/ ? "some-command" : "some-other-command")}' file.txt