I have an input file that contains only one line:
$ cat input
foo bar
I want to use this line in my script and there are 3 ways to get it that I know of:
line=$(cat input)
line=$(<input)
IFS= read -r line < input
For example, using command substitution means I spawn a subshell, whereas with read
I do not, correct? What other differences are there and is one way preferred over the others? I also noticed (with strace
) that only read
triggers the syscall openat
for some reason. How is it possible that the others don't?
$ strace ./script |& grep input
read(3, "#!/usr/bin/env bash\n\ncat > input"..., 80) = 80
read(255, "#!/usr/bin/env bash\n\ncat > input"..., 167) = 167
read(255, "\nline=$(cat input)\nline=$(<input"..., 167) = 60
read(255, "line=$(<input)\nIFS= read -r line"..., 167) = 41
read(255, "IFS= read -r line < input\n", 167) = 26
openat(AT_FDCWD, "input", O_RDONLY) = 3
line=$(cat input)
is the POSIX way of reading the entire file. It requires a fork.line=$(< input)
is a marginally more efficient Bashism for reading the entire file. It also forks, but doesn't have to execve.Not mentioned but
mapfile
/readarray
are significantly more efficient Bashisms for reading the entire file line-by-line into arrays. No forks.IFS= read -r line < input
is the POSIX way of reading a single line without a subshell. No forks.The reason why you only see the latter opening the file is simply that the others do it in a subshell, and you haven't specified
-f
to trace child processes.