在Bash中如何将多行字符串读入多个变量?

12
我该如何将一个包含三行的字符串分配给三个变量?
# 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}"

我尝试使用 echo ${s} | read a b c 替代 <<<,但仍然无法使其运行。

这种操作在bash中是否可行?


3
你可能想考虑使用mapfilereadarray,并使用数组而不是不同命名的变量来处理类似这样的事情。 - Eric Renouf
2个回答

11
默认的输入分隔符是 \n。
{ read a; read b; read c;} <<< "${s}"

-d char:允许指定另一个输入分隔符。
例如,如果输入字符串中没有SOH字符(ASCII码1)。
IFS=$'\n' read -r -d$'\1' a b c <<< "${s}"

我们将IFS设置为$'\n',因为IFS的默认值是:
$ printf "$IFS" | hd -c
00000000  20 09 0a                                          | ..|
0000000      \t  \n                                                    
0000003

编辑:-d可以接受一个空参数,-d和空参数之间必须有空格。
IFS=$'\n' read -r -d '' a b c <<< "${s}"
read内置文档可通过在bash提示符下键入help read来查看。
编辑:在评论中提到了解决多行输入的方法。
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[@]}"

另一种方法是不使用read函数。
IFS=$'\n'   # avoid space/tab splitting
set -f      # avoid file globbing
arr=($s)
# possibly restore defaults
IFS=$' \t\n'
set +f

这似乎是唯一的方法来让它运行起来。虽然不太优雅,也不能很好地扩展,但至少能工作。 - ssc
我不明白你所说的“不优雅”和“不可扩展”,这不是你问题的答案吗? - Nahuel Fouilleul
您说得对,这回答了我的问题;然而,我问题中的三行只是作为示例,我的实际用例有更多行,并且调用了 read 十次以读取十行,这看起来不太优雅 - 一个适用于任意行数的解决方案会更好地扩展。 - ssc

8
您需要使用readarray命令,而不是read
readarray -t lines <<< "$s"

理论上,这里不需要给$s加引号。但是如果你使用的是bash 4.4或更早版本,由于之前的一些bug,我仍然建议给它加上引号。

将这些行放在一个数组中后,如果需要,可以分配单独的变量。

a=${lines[0]}
b=${lines[1]}
c=${lines[2]}

这将是首选方法,但不幸的是它只适用于Linux,而在macOS上readarray(又名mapfile)仅在前者的Bash 4中可用,而在后者中不可用。 - ssc
在OS X上可以轻松安装最新版本的bash。系统bash基本上应该被视为POSIX shell的实现。 - chepner
我正在使用安装在OS X 10.11.6上的homebrew GNU bash版本为4.4.12(1)-release (x86_64-apple-darwin15.6.0)。足够新。 - ssc
这是我首选的解决方案。 - ketil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接