Não é possível canalizar o “mapfile” do bash… mas por quê?

8

Eu só quero colocar todos os arquivos em um determinado diretório em uma matriz bash (supondo que nenhum dos arquivos tenha uma nova linha no nome):

Então:

myarr=()
find . -maxdepth 1  -name "mysqldump*" | mapfile -t myarr; echo "${myarr[@]}"

Resultado vazio!

Se eu fizer a maneira indireta de usar um arquivo, temporário ou de outra forma:

myarr=()
find . -maxdepth 1  -name "mysqldump*" > X
mapfile -t myarray < X
echo "${myarray[@]}"

Resultado!

Mas por que o mapfile não lê corretamente de um cano?

    
por David Tonhofer 14.08.2018 / 13:13

3 respostas

21

Em man 1 bash :

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

Esses subshells herdam variáveis do shell principal, mas são independentes. Isso significa que mapfile em seu comando original opera em seu próprio myarr . Então echo (estando fora do canal) imprime vazio myarr (que é o myarr do shell principal).

Este comando funciona de maneira diferente:

find . -maxdepth 1 -name "mysqldump*" | { mapfile -t myarr; echo "${myarr[@]}"; }

Nesse caso, mapfile e echo operam no mesmo myarr (que não é o myarr do shell principal).

Para alterar o myarr do shell principal, você precisa executar exatamente mapfile no shell principal. Exemplo:

myarr=()
mapfile -t myarr < <(find . -maxdepth 1 -name "mysqldump*")
echo "${myarr[@]}"
    
por 14.08.2018 / 13:23
10

O Bash executa os comandos de um pipeline em um ambiente subshell, de forma que qualquer atribuição de variáveis, etc., que ocorra dentro dele não seja visível para o restante do shell.

Dash (% s /bin/sh do Debian) e sh do busybox são semelhantes, enquanto zsh e ksh execute a última parte no shell principal. No Bash, você pode usar shopt -s lastpipe para fazer o mesmo, mas ele só funciona quando o controle de trabalho está desativado, portanto, não em shells interativos por padrão.

Então:

$ 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 e mapfile têm o mesmo problema.)

Como alternativa (e mencionada por Attie), use substituição de processos , que funciona como um pipe generalizado e é suportado em Bash, ksh e zsh.

$ bash -c 'x=a; read x < <(echo b); echo $x'
b

O POSIX não é especificado se as partes de um pipeline são executadas em subshells ou não, então não se pode realmente dizer que qualquer um dos shells estaria "errado" nisso.

    
por 14.08.2018 / 14:39
8

Como Kamil apontou, cada elemento no pipeline é um processo separado.

Você pode usar a seguinte substituição de processo para que find seja executado em um processo diferente, com a invocação de mapfile restante no seu interpretador atual, permitindo acesso a myarr posteriormente:

myarr=()
mapfile -t myarr < <( find . -maxdepth 1  -name "mysqldump*" )
echo "${myarr[@]}"

b < <( a ) agirá de forma semelhante a a | b em termos de como o pipeline está conectado - a diferença é que b é executado " aqui ".

    
por 14.08.2018 / 13:31

Tags