Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bash: read multi-line string into multiple variables

Tags:

string

bash

How can I assign a newline-separated string with e.g. three lines to three variables ?

# test string
s='line 01
line 02
line 03'

# this doesn't seem to make any difference at all
IFS=$'\n'

# first naive attempt
read a b c <<< "${s}"

# this prints 'line 01||':
# everything after the first newline is dropped
echo "${a}|${b}|${c}"

# second attempt, remove quotes
read a b c <<< ${s}

# this prints 'line 01 line 02 line 03||':
# everything is assigned to the first variable
echo "${a}|${b}|${c}"

# third attempt, add -r
read -r a b c <<< ${s}

# this prints 'line 01 line 02 line 03||':
# -r switch doesn't seem to make a difference
echo "${a}|${b}|${c}"

# fourth attempt, re-add quotes
read -r a b c <<< "${s}"

# this prints 'line 01||':
# -r switch doesn't seem to make a difference
echo "${a}|${b}|${c}"

I also tried using echo ${s} | read a b c instead of <<<, but couldn't get that to work either.

Can this be done in bash at all ?

like image 299
ssc Avatar asked May 22 '17 11:05

ssc


2 Answers

read default input delimiter is \n

{ read a; read b; read c;} <<< "${s}"

-d char : allows to specify another input delimiter

For example is there is no character SOH (1 ASCII) in input string

IFS=$'\n' read -r -d$'\1' a b c <<< "${s}"

We set IFS to $'\n' because IFS default value is :

$ printf "$IFS" | hd -c
00000000  20 09 0a                                          | ..|
0000000      \t  \n                                                    
0000003

EDIT: -d can take a null argument the space is mandatory between -d and null argument:

IFS=$'\n' read -r -d '' a b c <<< "${s}"

The read builtin documentation is available by typing help read at the bash prompt.

EDIT: after comment about a solution for any number of lines

function read_n {
    local i s n line
    n=$1
    s=$2
    arr=()
    for ((i=0;i<n;i+=1)); do
        IFS= read -r line
        arr[i]=$line
    done <<< "${s}"
}

nl=$'\n'
read_n 10 "a${nl}b${nl}c${nl}d${nl}e${nl}f${nl}g${nl}h${nl}i${nl}j${nl}k${nl}l"

printf "'%s'\n" "${arr[@]}"
like image 72
Nahuel Fouilleul Avatar answered Sep 21 '22 22:09

Nahuel Fouilleul


You are looking for the readarray command, not read.

readarray -t lines <<< "$s"

(Theoretically, $s does not need to be quoted here. Unless you are using bash 4.4 or later, I would quote it anyway, due to some bugs in previous versions of bash.)

Once the lines are in an array, you can assign the separate variables if you need to

a=${lines[0]}
b=${lines[1]}
c=${lines[2]}
like image 43
chepner Avatar answered Sep 20 '22 22:09

chepner