Как выполнить команду для каждой строки файла?

Например, прямо сейчас я использую следующее, чтобы изменить пару файлов, чьи пути Unix я написал в файл:

cat file.txt | while read in; do chmod 755 "$in"; done

Есть ли более элегантный, безопасный способ?

Ответ 1

Читайте файл построчно и выполняйте команды: 4 ответа

Это потому, что есть не только 1 ответ...

  1. shell расширение командной строки
  2. xargs специальный инструмент
  3. while read с некоторыми замечаниями
  4. while read -u с использованием выделенного fd для интерактивной обработки (пример)

Что касается запроса OP: запуск chmod для всех целей, перечисленных в файле, xargs является указанным инструментом. Но для некоторых других приложений небольшое количество файлов и т.д....

  1. Читать весь файл как аргумент командной строки.

    Если ваш файл не слишком большой и все файлы имеют правильные имена (без пробелов и других специальных символов, таких как кавычки), вы можете использовать расширение командной строки shell. Просто:

    chmod 755 $(<file.txt)
    

    Для небольшого количества файлов (строк) эта команда является более легкой.

  2. xargs - правильный инструмент

    Для большего количества файлов или почти для любого количества строк во входном файле...

    Для многих инструментов binutils, таких как chown, chmod, rm, cp -t...

    xargs chmod 755 <file.txt
    

    Если у вас есть специальные символы и/или много строк в file.txt.

    xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)
    

    если ваша команда должна быть выполнена ровно 1 раз по записи:

    xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)
    

    Это не нужно для этого примера, поскольку chmod принимает несколько файлов в качестве аргумента, но это соответствует названию вопроса.

    В некоторых особых случаях вы можете даже определить местоположение аргумента файла в командах, сгенерированных xargs:

    xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)
    

    Тест с seq 1 5 в качестве ввода

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

    xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
    Blah 1 blabla 1..
    Blah 2 blabla 2..
    Blah 3 blabla 3..
    Blah 4 blabla 4..
    Blah 5 blabla 5..
    

    Где командование выполняется один раз в строке.

  3. while read и варианты.

    Как предполагает OP, cat file.txt | while read in; do chmod 755 "$in"; done будет работать, но есть 2 проблемы:

    • cat | - бесполезная вилка, и

    • | while ... ;done станет недоработкой, и после ;done среда исчезнет.

    Так что это может быть лучше написано:

    while read in; do chmod 755 "$in"; done < file.txt
    

    Но,

    • Вас могут предупредить о флагах $IFS и read:

      help read
      
      read: read [-r] ... [-d delim] ... [name ...]
          ...
          Reads a single line from the standard input... The line is split
          into fields as with word splitting, and the first word is assigned
          to the first NAME, the second word to the second NAME, and so on...
          Only the characters found in $IFS are recognized as word delimiters.
          ...
          Options:
            ...
            -d delim   continue until the first character of DELIM is read, 
                       rather than newline
            ...
            -r do not allow backslashes to escape any characters
          ...
          Exit Status:
          The return code is zero, unless end-of-file is encountered...
      

      В некоторых случаях вам может понадобиться использовать

      while IFS= read -r in;do chmod 755 "$in";done <file.txt
      

      Для избежания проблем с чужими именами файлов. И, может быть, если у вас возникнут проблемы с UTF-8:

      while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
      
    • Пока вы используете STDIN для чтения file.txt, ваш сценарий не может быть интерактивным (вы больше не можете использовать STDIN).

  4. while read -u, используя выделенный fd.

    Синтаксис: while read ...;done <file.txt перенаправит STDIN в file.txt. Это означает, что вы не сможете иметь дело с процессом, пока он не закончится.

    Если вы планируете создать интерактивный инструмент, вам следует избегать использования STDIN и использовать какой-либо альтернативный дескриптор файла.

    Дескрипторы файла констант: 0 для STDIN, 1 для STDOUT и 2 для STDERR. Вы можете увидеть их по:

    ls -l /dev/fd/
    

    или

    ls -l /proc/self/fd/
    

    Оттуда вы должны выбрать неиспользуемый номер между 0 и 63 (на самом деле больше, в зависимости от инструмента суперпользователя sysctl) в качестве дескриптора файла:

    Для этой демонстрации я буду использовать fd 7:

    exec 7<file.txt      # Without spaces between '7' and '<'!
    ls -l /dev/fd/
    

    Тогда вы можете использовать read -u 7 следующим образом:

    while read -u 7 filename;do
        ans=;while [ -z "$ans" ];do
            read -p "Process file '$filename' (y/n)? " -sn1 foo
            [ "$foo" ]&& [ -z "${foo/[yn]}" ]&& ans=$foo || echo '??'
        done
        if [ "$ans" = "y" ] ;then
            echo Yes
            echo "Processing '$filename'."
        else
            echo No
        fi
    done
    

    Чтобы закрыть fd/7:

    exec 7<&-            # This will close file descriptor 7.
    ls -l /dev/fd/
    

Ответ 2

Да.

while read in; do chmod 755 "$in"; done < file.txt

Таким образом вы можете избежать процесса cat.

cat почти всегда плох для такой цели. Вы можете больше узнать о Бесполезное использование кошки

Ответ 3

если у вас есть хороший селектор (например, все .txt файлы в каталоге) вы можете сделать:

for i in *.txt; do chmod 755 "$i"; done

bash для цикла

или ваш вариант:

while read line; do chmod 755 "$line"; done <file.txt

Ответ 4

Если вы знаете, что на входе нет пробелов:

xargs chmod 755 < file.txt

Если в путях могут быть пробелы, и если у вас есть GNU xargs:

tr '\n' '\0' < file.txt | xargs -0 chmod 755

Ответ 5

Если вы хотите запускать свою команду параллельно для каждой строки, вы можете использовать GNU Parallel

parallel -a <your file> <program>

Каждая строка вашего файла будет передана программе в качестве аргумента. По умолчанию parallel запускает столько потоков, сколько подсчитывает ваш процессор. Но вы можете указать его с помощью -j

Ответ 6

Я вижу, что вы отметили bash, но Perl также будет хорошим способом сделать это:

perl -p -e '`chmod 755 $_`' file.txt

Вы также можете применить регулярное выражение, чтобы убедиться, что вы получаете нужные файлы, например. обрабатывать только файлы .txt:

perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt

Чтобы "просмотреть", что происходит, просто замените обратные ссылки на двойные кавычки и добавьте print:

perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt

Ответ 7

Вы также можете использовать AWK, что даст вам больше возможностей для работы с файлом

awk '{ print "chmod 755 "$0"" | "/bin/sh"}' file.txt

если в вашем файле есть разделитель полей, например:

field1,field2,field3

Чтобы получить только первое поле, которое вы делаете

awk -F, '{ print "chmod 755 "$1"" | "/bin/sh"}' file.txt

Вы можете проверить более подробную информацию о документации GNU.https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple

Ответ 8

Логика относится ко многим другим целям. А как читать .sh_history каждого пользователя из /home/filesystem? Что, если их будет тысяча?

#!/bin/ksh
last |head -10|awk '{print $1}'|
 while IFS= read -r line
 do
su - "$line" -c 'tail .sh_history'
 done

Вот сценарий https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh