我只是想将某个目录中的所有文件放入 bash 数组中(假设所有文件的名称中都没有换行符):
所以:
myarr=()
find . -maxdepth 1 -name "mysqldump*" | mapfile -t myarr; echo "${myarr[@]}"
结果为空!
如果我以迂回的方式使用文件(临时文件或其他文件):
myarr=()
find . -maxdepth 1 -name "mysqldump*" > X
mapfile -t myarray < X
echo "${myarray[@]}"
结果!
但是为什么不能mapfile
正确从管道读取?
答案1
管道中的每个命令都作为单独的进程执行(即在子 shell 中)。
此类子 shell 会从主 shell 继承变量,但它们是独立的。这意味着mapfile
您的原始命令会自行运行myarr
。然后echo
(在管道外部)打印空myarr
(这是主 shell 的myarr
)。
此命令的工作方式不同:
find . -maxdepth 1 -name "mysqldump*" | { mapfile -t myarr; echo "${myarr[@]}"; }
在这种情况下mapfile
,和echo
对相同的进行操作myarr
(这不是主 shell 的myarr
)。
要更改主 shell,myarr
您必须mapfile
在主 shell 中准确运行。例如:
myarr=()
mapfile -t myarr < <(find . -maxdepth 1 -name "mysqldump*")
echo "${myarr[@]}"
答案2
Bash 在子 shell 环境中运行管道的命令,因此其中发生的任何变量赋值等对于 shell 的其余部分来说都是不可见的。
Dash(Debian 的/bin/sh
)以及 busyboxsh
类似,而 zsh 和 ksh 在主 shell 中运行最后一部分。在 Bash 中,您可以使用它shopt -s lastpipe
来执行相同操作,但它仅在禁用作业控制时才有效,因此默认情况下在交互式 shell 中不起作用。
所以:
$ bash -c 'x=a; echo b | read x; echo $x'
a
$ bash -c 'shopt -s lastpipe; x=a; echo b | read x; echo $x'
b
(read
并且mapfile
有同样的问题。)
或者(和如上所述由 Attie 撰写),使用流程替代,其工作方式类似于通用管道,并且在 Bash、ksh 和 zsh 中受支持。
$ bash -c 'x=a; read x < <(echo b); echo $x'
b
POSIX 没有指定管道的各个部分是否在子 shell 中运行,因此不能说任何一个 shell 在这方面是“错误的”。
答案3
正如卡米尔 (Kamil) 指出的那样,管道中的每个元素都是一个单独的过程。
您可以使用以下流程替代以便find
在不同的进程中运行,调用保留在当前解释器中,从而允许之后mapfile
访问:myarr
myarr=()
mapfile -t myarr < <( find . -maxdepth 1 -name "mysqldump*" )
echo "${myarr[@]}"
b < <( a )
其行为与a | b
管道连接方式类似 - 不同之处在于b
执行“这里“。
答案4
我不知道为什么,但看看shellcheck
SC2206:引用以防止单词分裂/通配,或使用mapfile
或进行稳健分裂read -a
。, 我可以看到如何为了使您的示例正常工作:使用 herestring( <<<
),如下所示:
# For bash mapfile -t array <<< "$var"
因此,针对您的具体情况,请执行以下操作:
# Capture all `find` output into a regular "indexed" bash array by using
# `mapfile` and a herestring (`<<<`)
mapfile -t array <<< "$(find . -maxdepth 1 -name "mysqldump*")"
# Print all elements at once in a big blob (this also demonstrates how
# to pass all elements in the array as arguments to another function)
echo "${array[@]}"
# OR, print all elements nicely, one element per line
for element in "${array[@]}"; do
echo " $element"
done
# OR, the same thing as just above but as a one-liner:
for element in "${array[@]}"; do echo " $element"; done