无法通过 bash 的“mapfile”输入...但是为什么呢?

无法通过 bash 的“mapfile”输入...但是为什么呢?

我只是想将某个目录中的所有文件放入 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

man 1 bash

管道中的每个命令都作为单独的进程执行(即在子 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

我不知道为什么,但看看shellcheckSC2206:引用以防止单词分裂/通配,或使用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

相关内容