Як запускати команду для кожного рядка файлу?


161

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

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

Чи є більш елегантний, безпечніший спосіб?

Відповіді:


127

Прочитайте файл за рядком і виконайте команди: 4 відповіді

Це тому, що існує не одна відповідь ...

  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/

    Нота: Я пускаю вражену версію, оскільки цей синтаксис може бути корисним, коли ви робите багато вводу-виводу з паралельними процесами:

    mkfifo sshfifo
    exec 7> >(ssh -t user@host sh >sshfifo)
    exec 6<sshfifo

3
Як і xargsбуло побудовано для відповіді на подібні потреби, деякі функції, такі як команда побудови якомога довше в поточному середовищі, щоб викликати chmodв цьому випадку якомога менше, зменшуючи вилки, забезпечують ефективність. while ;do..done <$fileimplie працює 1 виделкою на 1 файл. xargsміг би запустити 1 вилку для тисячі файлів ... надійно.
Ф. Хаурі

1
чому третя команда не працює в makefile? Я отримую "синтаксичну помилку біля несподіваного маркера` <'", але виконання прямо з командного рядка працює.
Вудро Барлоу

2
Це здається пов’язаним із конкретним синтаксисом Makefile. Ви можете спробувати змінити командний рядок:cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
F. Hauri,

@ 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майже завжди поганий для такої мети, як ця. Ви можете прочитати більше про марне використання кота.


Уникайте одна cat хороша ідея, але в цьому випадку, зазначена командаxargs
F. Hauri

Це посилання не здається релевантним, можливо, зміст веб-сторінки змінився? Решта відповіді дивовижна, хоча :)
starbeamrainbowlabs

@starbeamrainbowlabs Так. Здається, сторінку переміщено. Я відновив зв’язок, і зараз повинно бути все в порядку. Дякую :)
ПП

1
Дякую! Це було корисно, особливо коли вам потрібно зробити щось інше, ніж викликати chmod(тобто реально виконати одну команду для кожного рядка у файлі).
Пер Лундберг

будьте обережні з накидами! від 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

Що не працює, це те, що якщо в рядку є пробіли, введення розділено на пробіли, а не на рядки.
Майкл Фокс

@Michael Fox: рядки з пробілами можна підтримувати, змінюючи роздільник. Щоб змінити його на нові рядки, встановіть змінну середовища 'IFS' перед скриптом / командою. Наприклад: експортувати IFS = '$ \ n'
codeniffer

Друкарня в моєму останньому коментарі. Повинно бути: експортувати IFS = $ '\ n'
codeniffer

14

Якщо ви знаєте, що у вводі немає жодного пробілу:

xargs chmod 755 < file.txt

Якщо в шляхах може бути пробіл і якщо у вас є xargs GNU:

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

Я знаю про xargs, але (на жаль) це здається менш надійним рішенням, ніж вбудовані функції bash, такі як "час" та "читати". Крім того, у мене немає Xargs GNU, але я використовую OS X і xargs також має тут варіант -0. Дякую за відповідь.
яструб

1
@hawk Ні: xargsнадійний. Цей інструмент дуже старий, і його код сильно переглядається . Його метою було спочатку створити лінії щодо обмежень оболонок (64 ккал / лінія чи щось таке). Тепер цей інструмент може працювати з дуже великими файлами і може значно зменшити кількість форк до остаточної команди. Дивіться мою відповідь та / або man xargs.
Ф. Хаурі

@hawk Менше надійного рішення в який спосіб? Якщо він працює в Linux, Mac / BSD та Windows (так, пакети MSYSGIT GNU xargs), то це так само надійно, наскільки це виходить.
Каміло Мартін

1
Для тих, хто все ще знаходить це в результатах пошуку ... ви можете встановити GNU xargs на macOS за допомогою Homebrew ( brew install findutils), а потім викликати xargs GNU за допомогою gxargs, наприкладgxargs chmod 755 < file.txt
Jase

13

Якщо ви хочете виконувати свою команду паралельно для кожного рядка, ви можете використовувати паралель GNU

parallel -a <your file> <program>

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


3

Я бачу, що ви помітили баш, але 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

2
Навіщо використовувати зворотні посилання У Perl є chmodфункція
glenn jackman

1
Ви хочете perl -lpe 'chmod 0755, $_' file.txt- використовуйте -lдля функції "auto-chomp"
glenn jackman

1

Ви також можете скористатись AWK, що надасть вам більше гнучкості в обробці файлу

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

якщо у вашому файлі є роздільник полів:

поле1, поле2, поле3

Щоб отримати лише перше поле, яке ви виконуєте

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

Ви можете ознайомитись з додатковою інформацією на GNU Documentation https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple



0

Я знаю, що пізно, але все ж

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

cat file | tr -d '\r' | xargs -L 1 -i echo do_sth_with_{}_as_line
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.