228

Say, I have a file foo.txt specifying N arguments

arg1
arg2
...
argN

which I need to pass to the command my_command

How do I use the lines of a file as arguments of a command?

nbro
  • 15,395
  • 32
  • 113
  • 196
Yoo
  • 17,526
  • 6
  • 41
  • 47
  • 16
    "arguments", plural, or "argument", single? The accepted answer is correct only in the single-argument case -- which is what the body text inquires about -- but not in the multi-argument case, on which the topic appears to inquire. – Charles Duffy Jun 05 '14 at 12:43
  • The 4 answers below usually have identical results, yet they have slightly different semantics. Now I remember why I stopped writing bash scripts :P – Navin Jan 07 '16 at 15:15

12 Answers12

305

If your shell is bash (amongst others), a shortcut for $(cat afile) is $(< afile), so you'd write:

mycommand "$(< file.txt)"

Documented in the bash man page in the 'Command Substitution' section.

Alterately, have your command read from stdin, so: mycommand < file.txt

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • 24
    To be pedantic, it's not a shortcut to use the `<` operator. It means that the shell itself will perform the redirection rather than executing the `cat` binary for it's redirection properties. – lstyls Oct 30 '17 at 23:48
  • Is there a limit to the number of arguments? If so, how can I overcome it? – a06e Nov 24 '17 at 12:31
  • 2
    It's a kernel setting so you'd need to recompile the kernel. Or investigate the xargs command – glenn jackman Nov 24 '17 at 13:57
  • Can you explain to me why I need the `$` here? – ykaner Jun 28 '18 at 19:14
  • Because it is required by the bash syntax as documented in the manual: [Command Substitution](https://www.gnu.org/software/bash/manual/bash.html#Command-Substitution) – glenn jackman Jun 28 '18 at 20:53
  • 4
    @ykaner because without `"$(…)"`, the contents of `file.txt` would be passed to the standard input of `mycommand`, not as an argument. `"$(…)"` means _run the command and give back the output as a string_; here the “command” only reads the file but it could be more complex. – törzsmókus Feb 07 '19 at 13:39
  • 9
    It's not working for me unless I lose the quotes. With the quotes, it takes the whole file as 1 argument. Without quotes, it interprets each line as a separate arg. – Pete Jun 29 '19 at 02:57
  • Without the quotes, it takes each **word** in the file as a separate arg, plus each word is subject to globbing - if one of the words is `*` then that expands to all the filenames in the current directory. – glenn jackman Jun 29 '19 at 10:52
  • 1
    @lstyls: I'm not sure what you mean by "it's not a shortcut." Having the shell perform the redirection instead of spawning a child `cat` process sounds like it would save resources. The ref man describes this substitution as "equivalent but faster." Maybe you mean "it's not just a shorter way of typing the same thing"? – LarsH Nov 19 '19 at 22:53
  • 1
    @LarsH you’re absolutely correct. That’s a less confusing way to put it, thanks. – lstyls Nov 20 '19 at 23:02
  • So perhaps "a builtin alternative for" would be more accurate. – glenn jackman Nov 20 '19 at 23:22
45

As already mentioned, you can use the backticks or $(cat filename).

What was not mentioned, and I think is important to note, is that you must remember that the shell will break apart the contents of that file according to whitespace, giving each "word" it finds to your command as an argument. And while you may be able to enclose a command-line argument in quotes so that it can contain whitespace, escape sequences, etc., reading from the file will not do the same thing. For example, if your file contains:

a "b c" d

the arguments you will get are:

a
"b
c"
d

If you want to pull each line as an argument, use the while/read/do construct:

while read i ; do command_name $i ; done < filename
Will
  • 3,500
  • 4
  • 30
  • 38
  • I should have mentioned, I am assuming that you are using bash. I realize that there are other shells out there, but almost all of the *nix machines I have worked on either ran bash or some equivalent. IIRC, this syntax should work the same on ksh and zsh. – Will Nov 19 '10 at 21:43
  • 1
    Read should be `read -r` unless you want to expand backslash-escape sequences -- and NUL is a safer delimiter to use than the newline, particularly if the arguments you're passing are things like filenames, which can contain literal newlines. Also, without clearing IFS, you get leading and trailing whitespace implicitly cleared from `i`. – Charles Duffy Jun 05 '14 at 12:41
32
command `< file`

will pass file contents to the command on stdin, but will strip newlines, meaning you couldn't iterate over each line individually. For that you could write a script with a 'for' loop:

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

Or (the multi-line variant):

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

Or (multi-line variant with $() instead of ``):

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

References:

  1. For loop syntax: https://www.cyberciti.biz/faq/bash-for-loop/
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Wesley Rice
  • 2,711
  • 19
  • 8
  • Also, putting `< file` in backticks means it doesn't actually perform a redirection for `command` at all. – Charles Duffy May 30 '17 at 17:25
  • 4
    (Did the people who upvoted this actually test it? ```command `< file` ``` **doesn't work** in either bash or POSIX sh; zsh is a different matter, but that's not the shell this question is about). – Charles Duffy Jul 08 '17 at 13:42
  • @CharlesDuffy Can you be more specific about how it doesn't work? – mwfearnley Apr 26 '18 at 11:12
  • @CharlesDuffy This does work in bash 3.2 and zsh 5.3. This won't work in sh, ash and dash, but neither $(< file). – Arnie97 Aug 16 '19 at 01:36
  • 1
    @mwfearnley, happy to provide that specificity. The goal is to iterate over lines, right? Put `Hello World` on one line. You'll see it first run `some_command Hello`, then `some_command World`, *not* `some_command "Hello World"` or `some_command Hello World`. – Charles Duffy Aug 16 '19 at 02:26
  • The for loop option here works great! I just added some multi-line variants of it to help the next person. Tested it with calling `git add` on a bunch of lines to add that were stored in a file. – Gabriel Staples Feb 18 '20 at 08:17
  • Actually...it doesn't properly parse lines which have spaces in them. So, instead of editing this answer yet again and completely corrupting it from the author's original answer, I have added my own answer here! I like it the best of all now. It's completely borrowed from another site (and cited) though anyway. Here's my answer: https://stackoverflow.com/questions/4227994/how-do-i-use-the-lines-of-a-file-as-arguments-of-a-command/60276836#60276836. – Gabriel Staples Feb 18 '20 at 08:43
16

You do that using backticks:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
  • 1,533
  • 1
  • 10
  • 7
  • 4
    This doesn't create *an* argument -- because it isn't quoted, it's subject to string-splitting, so if you emitted `echo "Hello * Starry * World" > file.txt` in the first step, you'd get at least four separate arguments passed to the second command -- and likely more, as the `*`s would expand to the names of files present in the current directory. – Charles Duffy Jul 08 '17 at 13:41
  • 3
    ...and because it's running glob expansion, it doesn't emit *exactly* what's in the file. **And** because it's performing a command substitution operation, it's extremely inefficient -- it's actually `fork()`ing off a subshell with a FIFO attached to its stdout, then invoking `/bin/cat` as a child of that subshell, then reading the output through the FIFO; compare to `$( – Charles Duffy Jul 08 '17 at 13:43
15

If you want to do this in a robust way that works for every possible command line argument (values with spaces, values with newlines, values with literal quote characters, non-printable values, values with glob characters, etc), it gets a bit more interesting.


To write to a file, given an array of arguments:

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

...replace with "argument one", "argument two", etc. as appropriate.


To read from that file and use its contents (in bash, ksh93, or another recent shell with arrays):

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

To read from that file and use its contents (in a shell without arrays; note that this will overwrite your local command-line argument list, and is thus best done inside of a function, such that you're overwriting the function's arguments and not the global list):

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

Note that -d (allowing a different end-of-line delimiter to be used) is a non-POSIX extension, and a shell without arrays may also not support it. Should that be the case, you may need to use a non-shell language to transform the NUL-delimited content into an eval-safe form:

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 "$@"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
9

If all you need to do is to turn file arguments.txt with contents

arg1
arg2
argN

into my_command arg1 arg2 argN then you can simply use xargs:

xargs -a arguments.txt my_command

You can put additional static arguments in the xargs call, like xargs -a arguments.txt my_command staticArg which will call my_command staticArg arg1 arg2 argN

Cobra_Fast
  • 15,671
  • 8
  • 57
  • 102
7

None of the answers seemed to work for me or were too complicated. Luckily, it's not complicated with xargs (Tested on Ubuntu 20.04).

This works with each arg on a separate line in the file as the OP mentions and was what I needed as well.

cat foo.txt | xargs my_command

One thing to note is that it doesn't seem to work with aliased commands.

The accepted answer works if the command accepts multiple args wrapped in a string. In my case using (Neo)Vim it does not and the args are all stuck together.

xargs does it properly and actually gives you separate arguments supplied to the command.

PhilT
  • 4,166
  • 1
  • 36
  • 26
  • `< foo.txt xargs my_command` or `xargs < foo.txt my_command`, the redirection can be anywhere. You may want to `< foo xargs -d \\n cmd` or `< foo tr \\n \\0 | xargs -0 cmd` for BSD – CervEd Jun 01 '23 at 09:12
6

Here's how I pass contents of a file as an argument to a command:

./foo --bar "$(cat ./bar.txt)"
chovy
  • 72,281
  • 52
  • 227
  • 295
3

I suggest using:

command $(echo $(tr '\n' ' ' < parameters.cfg))

Simply trim the end-line characters and replace them with spaces, and then push the resulting string as possible separate arguments with echo.

fxmarty
  • 71
  • 5
2

In my bash shell the following worked like a charm:

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

where input_file is

arg1
arg2
arg3

As evident, this allows you to execute multiple commands with each line from input_file, a nice little trick I learned here.

Community
  • 1
  • 1
cydonian
  • 1,686
  • 14
  • 22
  • 3
    Your "nice little trick" is dangerous from a security perspective -- if you have an argument containing `$(somecommand)`, you'll get that command executed rather than passed through as text. Likewise, `>/etc/passwd` will be processed as a redirection and overwrite `/etc/passwd` (if run with appropriate permissions), etc. – Charles Duffy May 30 '17 at 17:22
  • 2
    It's much safer to do the following instead (on a system with GNU extensions): `xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _` -- and also more efficient, since it passes as many arguments to each shell as possible rather than starting one shell per line in your input file. – Charles Duffy May 30 '17 at 17:24
  • And of course, lose the [useless use of `cat`](/questions/11710552/useless-use-of-cat) – tripleee Aug 09 '19 at 11:05
1

Both solutions work even when lines have spaces:

readarray -t my_args < foo.txt
my_command "${my_args[@]}"

if readarray doesn't work, replace it with mapfile, they're synonyms.

I formerly tried this one below, but had problems when my_command was a script:

xargs -d '\n' -a foo.txt my_command
André Willik Valenti
  • 1,702
  • 14
  • 21
0

After editing @Wesley Rice's answer a couple times, I decided my changes were just getting too big to continue changing his answer instead of writing my own. So, I decided I need to write my own!

Read each line of a file in and operate on it line-by-line like this:

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

This comes directly from author Vivek Gite here: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/. He gets the credit!

Syntax: Read file line by line on a Bash Unix & Linux shell:
1. The syntax is as follows for bash, ksh, zsh, and all other shells to read a file line by line
2. while read -r line; do COMMAND; done < input.file
3. The -r option passed to read command prevents backslash escapes from being interpreted.
4. Add IFS= option before read command to prevent leading/trailing whitespace from being trimmed -
5. while IFS= read -r line; do COMMAND_on $line; done < input.file


And now to answer this now-closed question which I also had: Is it possible to `git add` a list of files from a file? - here's my answer:

Note that FILES_STAGED is a variable containing the absolute path to a file which contains a bunch of lines where each line is a relative path to a file I'd like to do git add on. This code snippet is about to become part of the "eRCaGuy_dotfiles/useful_scripts/sync_git_repo_to_build_machine.sh" file in this project, to enable easy syncing of files in development from one PC (ex: a computer I code on) to another (ex: a more powerful computer I build on): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles.

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

References:

  1. Where I copied my answer from: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. For loop syntax: https://www.cyberciti.biz/faq/bash-for-loop/

Related:

  1. How to read contents of file line-by-line and do git add on it: Is it possible to `git add` a list of files from a file?
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265