xargs з перенаправленням stdin / stdout


21

Я хотів би запустити:

./a.out < x.dat > x.ans

для кожного * .dat файлу в каталозі A .

Звичайно, це можна зробити за сценарієм bash / python / будь-який, але мені подобається писати сексуальний однолінійний. Все, що я міг досягти, - це (все-таки без жодного виводу):

ls A/*.dat | xargs -I file -a file ./a.out

Але -a в xargs не розуміє файл "zamjenski".

Дякую за допомогу.


Окрім інших хороших відповідей тут, ви можете скористатися опцією -a GNU xargs.
Джеймс Янгмен

Відповіді:


30

Перш за все, не використовуйте lsвихід як список файлів . Використовуйте розширення оболонки або find. Нижче див. Можливі наслідки неправильного використання ls + xargs та приклад правильного xargsвикористання.

1. Простий спосіб: для циклу

Якщо ви хочете обробити лише файли, розміщені нижче A/, простого forциклу має бути достатньо:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 Чому ні   ls | xargs ?

Ось приклад того , як погані речі можуть звернутися , якщо ви використовуєте lsз xargsдля роботи. Розглянемо наступний сценарій:

  • спочатку створимо кілька порожніх файлів:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • перегляньте файли і що вони нічого не містять:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • запустити магічну команду, використовуючи xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • результат:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Так що вам просто вдалося перезаписати і те, mypreciousfile.datі mypreciousfile.dat.ans. Якби в цих файлах був якийсь вміст, він був би стертий.


2. Використання   xargs : належним чином с  find 

Якщо ви хочете наполягати на використанні xargs, використовуйте -0(нульові імена):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Зауважте дві речі:

  1. таким чином ви створите файли із .dat.ansзакінченням;
  2. це порушиться, якщо якесь ім’я файлу містить знак лапки ( ").

Обидва питання можна вирішити різним способом виклику оболонки:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Все зроблено всередині find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

Це, знову ж таки, створює .dat.ansфайли і порушиться, якщо імена файлів містять ". Щоб зробити це, використовуйте bashта змінюйте спосіб його виклику:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;

+1 для згадування про те, щоб не розбирати вихід ls.
rahmu

2
Варіант 2 перерви, коли
файли

2
Дуже хороший момент, дякую! Я відповідно оновлю.
rozcietrzewiacz

Хочу лише зазначити, що якщо zshвін використовується як оболонка (а SH_WORD_SPLIT не встановлений), усі неприємні спеціальні випадки (пробіли, "у назві файлу тощо") не повинні розглядатися. Тривіальне for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; doneпрацює у всіх випадках.
jofel

-1 тому що я намагаюся зрозуміти, як робити xargs зі stdin, і моє запитання не має нічого спільного з файлами або find.
Мехрдад

2

Спробуйте зробити щось подібне (синтаксис може дещо відрізнятися залежно від оболонки, яку ви використовуєте):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done


Це б не спрацювало. Він спробує оперувати такими матеріалами, як somefile.dat.datі перенаправити весь вихід у один файл.
rozcietrzewiacz

Ти правий. Я відредагував рішення, щоб його виправити.
rahmu

Гаразд - Майже добре :) Просто somefile.dat.ansвихідний матеріал виглядав би не дуже приємно.
rozcietrzewiacz

1
Відредаговано! Я не знав про "%". Це працює як шарм, дякую за пораду.
rahmu

1
Додавання значень -type fileбуло б непогано (не може < directory), і це робить незвичне ім'я файлу-феєрія сумним.
Мат

2

Для простих візерунків відповідна петля for:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Для більш складних випадків, коли вам потрібна інша утиліта для переліку файлів (zsh або bash 4 мають достатньо потужні шаблони, які вам рідко потрібно знайти, але якщо ви хочете залишитися в оболонці POSIX або використовувати швидку оболонку, як тире, вам знадобиться знайти що завгодно нетривіальне), хоча читання є найбільш підходящим:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Це буде обробляти пробіли, оскільки читання (за замовчуванням) орієнтоване на рядки. Він не буде обробляти нові рядки і не буде обробляти зворотні косої риси, тому що за замовчуванням він інтерпретує послідовності евакуації (що насправді дозволяє переходити в новий рядок, але знайти цей формат не може). У багатьох оболонках є -0можливість прочитати так, що в тих, з якими ви можете працювати з усіма символами, але, на жаль, це не POSIX.



1

Я думаю, вам потрібно принаймні виклик оболонки в xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Редагувати: Слід зазначити, що такий підхід не працює, коли файли містять пробіл. Не можу працювати. Навіть якщо ви використовували find -0 та xargs -0, щоб xargs правильно розумів пробіли, виклик -c оболонки буде перекривати їх. Однак ОП відверто попросив рішення xargs, і це найкраще рішення xargs, яке я придумав. Якщо пробіл у назви файлів може бути проблемою, використовуйте find -exec або цикл оболонки.



@rozcietrzewiacz: Загалом, звичайно, але я припускаю, що хтось, хто намагається зробити xargs voodoo, це знає. Оскільки ці назви файлів зазнають іншого розширення оболонки, у будь-якому випадку їх потрібно добре поводити.
титон

1
Ви помиляєтесь щодо розширення оболонки. lsвихід можна такі речі , як простору , не уникнути і що це проблема.
rozcietrzewiacz

@rozcietrzewiacz: Так, я розумію цю проблему. Тепер просто припустимо, що ви належним чином уникнете цих пробілів і вкажете їх у xargs: вони замінюються в рядку sh -c, sh токенізує рядок -c, і все порушується.
титон

Так. Ви бачите зараз, розбирати lsне корисно. Але xargs можна сміливо використовувати при знаходженні - дивіться пропозицію № 2 у моїй відповіді.
rozcietrzewiacz

1

Не потрібно ускладнювати це. Ви можете зробити це за допомогою forциклу:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

${file%.dat}.ansбіт буде видалити .datсуфікс імені файлу з файлу в $fileі замість того, щоб додати .ansв кінець.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.