8thperson
8thperson

Reputation: 417

split command output into array of lines

Here's my bash function to get a command's output as parameter and then return an array of output lines.

function get_lines { 
    while read -r line; do 
        echo $line
    done <<< $1
}

SESSIONS=`loginctl list-sessions`
get_lines "$SESSIONS"

Actual output of loginctl list-sessions is:

   SESSION        UID USER             SEAT            
        c2       1000 asif             seat0           
        c7       1002 sadia            seat0           

But the while loop only runs once printing all output in a single line. How can I get an array of lines and return it?

Upvotes: 3

Views: 1810

Answers (3)

webb
webb

Reputation: 4350

here's a way in bash v4+:

SESSIONS=`loginctl list-sessions`
mapfile -t myArray <<< "$SESSIONS"

ref:

Creating an array from a text file in BASH with mapfile

Upvotes: 1

mklement0
mklement0

Reputation: 440526

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

Upvotes: 1

andy
andy

Reputation: 31

You could use readarray and avoid the get_lines function:

readarray SESSIONS < <(loginctl --no-legend list-sessions)

this create the array SESSIONS with each line of the output of the command mapped to an element of the array.

Upvotes: 3

Related Questions