Bash – Command Substitution Splitting on Newline but Not Space

bashcommand-substitution

I know I can solve this problem several ways, but I'm wondering if there is a way to do it using only bash built-ins, and if not, what is the most efficient way to do it.

I have a file with contents like

AAA
B C DDD
FOO BAR

by which I only mean it has several lines and each line may or may not have spaces. I want to run a command like

cmd AAA "B C DDD" "FOO BAR"

If I use cmd $(< file) I get

cmd AAA B C DDD FOO BAR

and if I use cmd "$(< file)" I get

cmd "AAA B C DDD FOO BAR"

How do I get each line treated a exactly one parameter?

Best Answer

Portably:

set -f              # turn off globbing
IFS='
'                   # split at newlines only
cmd $(cat <file)
unset IFS
set +f

Or using a subshell to make the IFS and option changes local:

( set -f; IFS='
'; exec cmd $(cat <file) )

The shell performs field splitting and filename generation on the result of a variable or command substitution that is not in double quotes. So you need to turn off filename generation with set -f, and configure field splitting with IFS to make only newlines separate fields.

There's not much to be gained with bash or ksh constructs. You can make IFS local to a function, but not set -f.

In bash or ksh93, you can store the fields in an array, if you need to pass them to multiple commands. You need to control expansion at the time you build the array. Then "${a[@]}" expands to the elements of the array, one per word.

set -f; IFS=$'\n'
a=($(cat <file))
set +f; unset IFS
cmd "${a[@]}"
Related Question