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

161

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

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

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

ястреб
источник

Ответы:

127

Читайте файл построчно и выполняйте команды: 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 и варианты.

    По предложению ОП 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 7<file.txt

    done

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

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

    Примечание: я оставил зачеркнутую версию, потому что этот синтаксис может быть полезен при выполнении многих операций ввода-вывода с процессом Parallels:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo
Ф. Хаури
источник
3
Как xargsбыло initialy билд для ответа на этот вид потребности, некоторые функции, такие как команды построения как можно дольше в текущих условиях для вызова chmodв этом случае как можно меньше, уменьшая вилки обеспечивают Efficience. while ;do..done <$fileподразумевается запуск 1 форка на 1 файл. xargsмог запустить 1 форк для тысячи файлов ... надежным способом.
Ф. Хаури
1
почему третья команда не работает в make-файле? я получаю "синтаксическую ошибку рядом с неожиданным токеном` <'", но выполнение прямо из командной строки работает.
Вудро Барлоу,
2
Это похоже на синтаксис, связанный с Makefile. Вы можете попробовать перевернуть командную строку:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
Ф. Хаури
@ F.Hauri по некоторым причинам tr \\n \\0 <file.txt |xargs -0 [command]примерно на 50% быстрее, чем метод, который вы описали.
phil294
Октябрь 2019, новое редактирование, добавление образца интерактивного файлового процессора.
Ф. Хаури
150

Да.

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

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

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

PP
источник
Избегайте одна cat хорошая идея, но в этом случае, указанная командаxargs
F. Hauri
Эта ссылка, похоже, не актуальна, возможно, содержание веб-страницы изменилось? Остальная часть ответа удивительна, хотя :)
starbeamrainbowlabs
@starbeamrainbowlabs Да. Кажется, страница была перемещена. Я снова связался и теперь должен быть в порядке. Спасибо :)
PP
1
Спасибо! Это было полезно, особенно когда вам нужно сделать что-то еще, кроме вызова chmod(то есть действительно выполнить одну команду для каждой строки в файле).
За Лундберг
будьте осторожны с обратными слешами! from unix.stackexchange.com/a/7561/28160 - « read -rчитает одну строку из стандартного ввода ( readбез -rинтерпретации обратной косой черты, вы этого не хотите)».
Этот бразильский парень
16

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

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

удар за петлю

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

while read line; do chmod 755 "$line"; done <file.txt
d.raev
источник
Что не работает, так это то, что если в строке есть пробелы, вход делится на пробелы, а не на строку.
Майкл Фокс
@Michael Fox: строки с пробелами могут поддерживаться путем изменения разделителя. Чтобы изменить его на новые строки, установите переменную окружения 'IFS' перед сценарием / командой. Пример: экспорт IFS = '$ \ n'
codeniffer
Опечатка в моем последнем комментарии. Должно быть: экспортировать IFS = $ '\ n'
codesniffer
14

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

xargs chmod 755 < file.txt

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

tr '\n' '\0' < file.txt | xargs -0 chmod 755
Гленн Джекман
источник
Я знаю о xargs, но (к сожалению) это кажется менее надежным решением, чем встроенные функции bash, такие как while и read. Кроме того, у меня нет GNU xargs, но я использую OS X, и у xargs здесь также есть опция -0. Спасибо за ответ.
Ястреб
1
@ Ястреб Нет: xargsэто надежно . Этот инструмент очень старый и его код сильно пересмотрен . Его целью было изначально построить линии с учетом ограничений оболочки (64kchar / line или что-то в этом роде). Теперь этот инструмент может работать с очень большими файлами и может значительно уменьшить количество разветвлений до финальной команды. Смотрите мой ответ и / или man xargs.
Ф. Хаури
@hawk Меньше надежного решения, каким образом? Если он работает в Linux, Mac / BSD и Windows (да, MSYSGIT связывает GNU xargs), то он настолько надежен, насколько это возможно.
Камило Мартин
1
Для тех, кто все еще находит это в результатах поиска ... вы можете установить GNU xargs на macOS с помощью Homebrew ( brew install findutils), а затем gxargsвместо этого вызывать GNU xargs , напримерgxargs chmod 755 < file.txt
Jase
13

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

parallel -a <your file> <program>

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

janisz
источник
3

Я вижу, что вы пометили 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
1,618
источник
2
Зачем использовать галочки? Perl имеет chmodфункцию
Гленн Джекман
1
Вы бы хотели perl -lpe 'chmod 0755, $_' file.txt- использовать -lдля функции "авто-chomp"
Гленн Джекман
1

Вы также можете использовать 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

brunocrt
источник
0

Я знаю, что уже поздно, но все же

Если по какой-либо причине вы столкнетесь с сохраненным в Windows текстовым файлом \r\nвместо \n, вы можете запутаться в результате, если в качестве аргумента в вашей команде указан sth после строки чтения. Так что удалите \r, например:

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
EP
источник