Як використовувати рядки файлу як аргументи команди?


158

Скажімо, у мене є файл із foo.txtзазначенням Nаргументів

arg1
arg2
...
argN

яку мені потрібно передати команді my_command

Як використовувати рядки файлу як аргументи команди?


10
"аргументи", множина або "аргумент", одиничні? Прийнята відповідь є правильною лише у випадку з одним аргументом - саме про це запитує текст тексту - але не у випадку з аргументацією, в якому тема, як видається, запитує.
Чарльз Даффі

4 відповіді нижче, як правило, мають однакові результати, але вони мають дещо різну семантику. Тепер я пам’ятаю, чому я перестав писати баш сценарії: P
Навін

Відповіді:


236

Якщо ваша оболонка є bash (серед інших), ярлик для $(cat afile)є $(< afile), тож ви напишете:

mycommand "$(< file.txt)"

Документовано на сторінці bash man у розділі «Заміна команди».

Крім того, попросіть прочитати вашу команду з stdin, тому: mycommand < file.txt


11
Щоб бути педантичним, це не ярлик для використання <оператора. Це означає, що сама оболонка буде виконувати перенаправлення, а не виконувати catдвійкові властивості для її перенаправлення.
lstyls

2
Це налаштування ядра, тому вам потрібно буде перекомпілювати ядро. Або досліджуйте команду xargs
glenn jackman

3
@ykaner, оскільки без "$(…)"цього вміст file.txtбуде передано до стандартного вводу mycommand, а не як аргумент. "$(…)"означає запустити команду і повернути результат у вигляді рядка ; тут команда лише читає файл, але він може бути складнішим.
törzsmókus

3
Це не працює для мене, якщо я не втрачу цитати. З цитатами він сприймає весь файл як 1 аргумент. Без лапок, він інтерпретує кожен рядок як окремий аргумент.
Піт

1
@LarsH ти абсолютно прав. Це менш заплутаний спосіб сказати, дякую.
lstyls

37

Як уже згадувалося, ви можете використовувати лапки або $(cat filename).

Те, що не згадувалося, і я думаю, що важливо зазначити, це те, що ви повинні пам'ятати, що оболонка розбиває вміст цього файлу відповідно до пробілу, надаючи кожне "слово", яке воно знайде для вашої команди як аргумент. І хоча ви, можливо, зможете укладати аргументи командного рядка в лапки, щоб він міг містити пробіли, послідовності виходу тощо, читання з файлу не зробить те саме. Наприклад, якщо ваш файл містить:

a "b c" d

Ви отримаєте такі аргументи:

a
"b
c"
d

Якщо ви хочете вивести кожен рядок як аргумент, використовуйте конструкцію while / read / do:

while read i ; do command_name $i ; done < filename

Я мав би згадати, я припускаю, що ви використовуєте bash. Я усвідомлюю, що там є інші снаряди, але майже всі машини * nix, над якими я працював, або керували башмом, або іншим еквівалентом. IIRC, цей синтаксис повинен працювати однаково на ksh та zsh.
Буде чи

1
Читати слід, read -rякщо ви не хочете розширити послідовно-зворотні послідовності - і NUL є більш безпечним роздільником, ніж новий рядок, особливо якщо аргументи, які ви передаєте, - це такі речі, як імена файлів, які можуть містити буквальні нові рядки. Крім того, не очищаючи IFS, ви отримуєте проміжно очищені провідні та відсталі пробіли i.
Чарльз Даффі

18
command `< file`

передасть вміст файлу команді на stdin, але викреслить нові рядки, це означає, що ви не змогли повторити кожен рядок окремо. Для цього ви можете написати сценарій із циклом 'для':

for line in `cat input_file`; do some_command "$line"; done

Або (багаторядковий варіант):

for line in `cat input_file`
do
    some_command "$line"
done

Або (багаторядковий варіант $()замість ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Список літератури:

  1. Для синтаксису циклу: https://www.cyberciti.biz/faq/bash-for-loop/

Крім того, введення < fileзадників означає, що воно насправді не виконує переадресацію command.
Чарльз Даффі

3
(Невже люди, які підтримали це, насправді перевірили це? command `< file` Не працює ні в bash, ні в POSIX sh; zsh - інша справа, але це не оболонка, про яку йдеться у цьому питанні).
Чарльз Даффі

@CharlesDuffy Чи можете ви бути більш конкретними щодо того, як це не працює?
mwfearnley

@CharlesDuffy Це працює в bash 3.2 та zsh 5.3. Це не працюватиме в sh, ash та dash, але ні $ (<файл).
Arnie97

1
@mwfearnley, радий надати цю специфіку. Мета - перебрати лінії, правда? Поставте Hello Worldна одну лінію. Ви побачите, що він спочатку запущений some_command Hello, потім some_command World, ні, some_command "Hello World" або some_command Hello World.
Чарльз Даффі

15

Ви робите це за допомогою зворотних посилань:

echo World > file.txt
echo Hello `cat file.txt`

4
Це не створює в аргумент - тому що він не цитує, це предмет для рядка розщеплення, так що якщо ви випромінюєте echo "Hello * Starry * World" > file.txtна першому етапі, ви отримаєте принаймні чотири окремих аргументів , переданих другій команді - і , ймовірно , більше, оскільки *s пошириться на назви файлів, присутніх у поточному каталозі.
Чарльз Даффі

3
... а оскільки працює глобальне розширення, він не випромінює саме те, що у файлі. А тому що він виконує операцію підстановки команд, це вкрай неефективно - це насправді fork()відключення нижньої частини підшивки з FIFO, прикріпленим до її stdout, потім виклик /bin/catяк дочірній підзарядки, а потім зчитування результатів через FIFO; Порівняйте з $(<file.txt), який читає вміст файлу в bash безпосередньо, не включаючи підрозділи або FIFO.
Чарльз Даффі

11

Якщо ви хочете зробити це надійним способом, який працює для кожного можливого аргументу командного рядка (значення з пробілами, значення з новими рядками, значення з буквальними символами цитати, недруковані значення, значення з глобальними символами тощо), це отримує трохи більш цікавим.


Щоб записати у файл, задавши масив аргументів:

printf '%s\0' "${arguments[@]}" >file

... замінити на "argument one", "argument two"і т.д., відповідно.


Щоб читати з цього файлу і використовувати його вміст (у bash, ksh93 або іншому нещодавному оболонці з масивами):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Щоб читати з цього файлу і використовувати його вміст (в оболонці без масивів; зауважте, що це замінить ваш локальний список аргументів командного рядка, і, таким чином, найкраще робити всередині функції, так що ви перезаписуєте аргументи функції, а не глобальний список):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Зауважте, що -d(дозволяючи використовувати інший роздільник кінця рядка) є розширенням, яке не є POSIX, і оболонка без масивів може також не підтримувати його. У такому випадку вам може знадобитися використовувати мову без оболонки для перетворення вмісту, обмеженого NUL, у evalбезпечну форму:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"

6

Ось як я передаю вміст файлу як аргумент команді:

./foo --bar "$(cat ./bar.txt)"

1
Це - але по суті менш ефективно - ефективно еквівалентно $(<bar.txt)(при використанні оболонки, наприклад, bash, з відповідним розширенням). $(<foo)це особливий випадок: на відміну від звичайної заміни команд, як вона використовується $(cat ...), вона не відщеплює підпрограму для роботи, і, таким чином, уникає великих витрат.
Чарльз Даффі

3

Якщо все, що вам потрібно зробити, це повернути файл arguments.txtіз вмістом

arg1
arg2
argN

в my_command arg1 arg2 argNто ви можете просто використовувати xargs:

xargs -a arguments.txt my_command

Ви можете вводити додаткові статичні аргументи у xargsвиклик, наприклад, xargs -a arguments.txt my_command staticArgякий буде дзвонитиmy_command staticArg arg1 arg2 argN


1

У моїй баш-шкарлупі працювало як шарм:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

де вхідний_файл

arg1
arg2
arg3

Як очевидно, це дозволяє виконувати кілька команд у кожному рядку з input_file, хороший маленький трюк, який я тут навчився .


3
Ваша "хороша маленька хитрість" небезпечна з точки зору безпеки - якщо у вас є аргумент, що містить $(somecommand), ви отримаєте цю команду, а не передану як текст. Так само >/etc/passwdбуде оброблено як перенаправлення та перезапис /etc/passwd(якщо це буде виконано з відповідними дозволами) тощо.
Чарльз Даффі

2
Набагато безпечніше робити наступні натомість (у системі з розширеннями GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- і також ефективніше, оскільки вона передає якомога більше аргументів до кожної оболонки, а не запускає по одній оболонці на рядок у вхідному файлі.
Чарльз Даффі

І звичайно, програти марне використанняcat
триплета

0

Після редагування відповіді @Wesley Rice я кілька разів вирішив, що мої зміни стали занадто великими, щоб продовжувати змінювати його відповідь, а не писати свою. Отже, я вирішив, що потрібно написати своє!

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

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Це безпосередньо від автора Vivek Gite тут: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Він отримує кредит!

Синтаксис: Читання файлів по рядку на оболонці Bash Unix & Linux:
1. Синтаксис виглядає наступним чином для bash, ksh, zsh та всіх інших оболонок для читання файлу за рядком
2. while read -r line; do COMMAND; done < input.file
3. -rОпція передана команді read запобігає інтерпретації втечі зворотнього косого кута.
4. Додайте IFS=параметр перед командою читання, щоб запобігти обробці пробілів / кінцевих пробілів -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


А тепер, щоб відповісти на це закрите зараз питання, яке у мене також було: Чи можна `git add` список файлів з файлу? - ось моя відповідь:

Зауважте, що FILES_STAGEDце змінна, що містить абсолютний шлях до файлу, який містить купу рядків, де кожен рядок є відносним шляхом до файлу, який я хотів би зробити git add. Цей фрагмент коду стане частиною файлу "eRCaGuy_dotfiles / korisні_scripts / sync_git_repo_to_build_machine.sh" у цьому проекті, щоб легко синхронізувати файли в процесі розробки з одного ПК (наприклад: комп'ютер, на якому я коду) на інший (наприклад: a більш потужний комп'ютер, на якому я будую): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Список літератури:

  1. Куди я скопіював свою відповідь: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Для синтаксису циклу: https://www.cyberciti.biz/faq/bash-for-loop/

Пов'язані:

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