The value of this answer is in explaining the problem with the OP's code.
- The other answers show the use of Bash v4+ builtin mapfile
(or its effective alias, readarray
) for directly reading input line by line into the elements of an array, without the need for a custom shell function.
- In Bash v3.x, you can use IFS=$'\n' read -r -d '' -a lines < <(...),
, but note that empty lines will be ignored.
Your primary problem is that unquoted (non-double-quoted) use of $1
makes the shell apply word-splitting to its contents, which effectively normalizes all runs of whitespace - including newlines - to a single space each, resulting in a single input line to the while
loop.
Secondarily, using $input
unquoted applies this word-splitting again on output with echo
.
Finally, by using read
without setting $IFS
, the internal field separator, to the empty string - via IFS= read -r line
- leading and trailing whitespace is trimmed from each input line.
That said, you can simplify your function to read directly from stdin rather than taking arguments:
function get_lines {
while IFS= read -r line; do
printf '%s\n' "$line"
done
}
which you can then invoke as follows, using a process substitution:
get_lines < <(loginctl list-sessions)
Using a pipeline would work too, but get_lines
would then run in a subshell, which means that it can't set variables visible to the current shell:
loginctl list-sessions | get_lines