Existe algum caso em que mapfile
tenha benefícios arr+=(input)
?
Exemplos simples
nome da matriz mapfile, arr:
mkdir {1,2,3}
mapfile -t arr < <(ls)
declare -p arr
saída:
declare -a arr=([0])="1" [1]="2" [2]="3")
Editar:
título alterado para abaixo; o corpo tinha y
como nome do array, mas o título tinha arr
como nome, o que poderia gerar confusão.
y+=(entrada)
IFS=$'\n'
y+=($(ls))
declare -p y
saída:
declare -a y=([0])="1" [1]="2" [2]="3")
Uma vantagem mapfile
é que você não precisa se preocupar com a divisão de palavras, eu acho.
Por outro lado, você pode evitar a divisão de palavras definindo, IFS=$'\n'
embora para este exemplo não haja nada com que se preocupar.
O segundo exemplo parece mais fácil de escrever, alguma coisa que estou perdendo?
Eles não são a mesma coisa, mesmo depois
IFS=$'\n'
.No bash especificamente (embora essa sintaxe tenha sido emprestada do zsh¹):
(
arr+=( $(cmd) )
seria usado para anexar elementos à matriz; portanto, seria comparado comkeys=( -1 "${!arr[@]}" ); readarray -tO "$(( ${keys[@]: -1} + 1))" arr < <(cmd)
²).Faz:
cmd
em um subshell com seu stdout aberto na extremidade de escrita um canal.$IFS
variável especial. Para os caracteres em$IFS
que são caracteres de espaço em branco, como nova linha, o comportamento é mais complexo:printf '\n\n\na\n\n\nb\n\n\n'
é dividida em apenas dois elementos:a
eb
.noglob
,nullglob
,failglob
,extglob
,globasciiranges
,glabstar
,nocaseglob
. Isso se aplica às palavras que contêm caracteres como*
,?
,[
, e com algumas versões do bash\
, e mais seextglob
estiver habilitado.$arr
matriz.Exemplo:
Como você pode ver, o
$'foo\n\n\n\n*'
arquivo foi dividido emfoo
e expandido para a lista de arquivos no diretório de trabalho atual, o que explica por que obtemos ambos*
e , o mesmo que explica por que obtemos (mostrado como ) 3 vezes, pois há a linha em a saída de e é correspondida por ambos e .*
foo
$'foo\n\n\n\n*'
?x
\x
"\\x"
\x
ls
*
?x
Com o bash 5.0, obtemos:
Com
\x
apenas duas, masx
três vezes como naquela versão, a barra invertida era um operador globbing mesmo quando não seguido por um operador globbing, pois\x
um glob corresponde ax
.Após
shopt nocaseglob
, obtemos:Com mostrado 3 vezes, pois também
aX
corresponde .?x
Depois
shopt -s failglob
:E
arr=( $(echo '/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*') )
Fica sem memória depois de deixar seu sistema inutilizável por vários minutos.
Então, para resumir,
IFS=$'\n'; arr=( $(cmd) )
não armazena as linhas da saída decmd
no array, mas os nomes dos arquivos resultantes da expansão das linhas não vazias da saída dascmd
quais são tratados como padrões glob.Com
mapfile
ou seureadarray
apelido menos enganoso:cmd
em um subshell com seu stdout aberto na extremidade de escrita de um pipe.<(...)
é expandido para algo como/dev/fd/63
ou/proc/self/fd/63
onde63
está um descritor de arquivo do shell pai aberto na extremidade de leitura desse canal.<
redirecionamento abreviado para0<
, que /dev/fd/63 é aberto para leitura em fd 0, o que significa que o stdin dereadarray
também será o final de leitura desse tubo.readarray
lê cada linha desse canal (simultaneamente aocmd
escrever nele), descarta o delimitador de linha (-t
) e o armazena (até o primeiro NUL se ele contiver algum, pelo menos nas versões atuais do bash) em um novo elemento do arquivo$arr
.Portanto, no final
$arr
, assumindo quecmd
as saídas não NUL conterão o conteúdo de cada linha da saída decmd
, independentemente de estarem vazias ou não, de conterem caracteres glob ou não.Com o exemplo acima:
Isso é consistente com o que vimos na saída
ls | cat
anterior, mas ainda está errado se a intenção for obter a lista de arquivos no diretório de trabalho atual. A saída dels
não pode ser pós-processada, a menos que você use algumas extensões da implementação GNU dels
versões--quoting-style=shell-always
recentes--zero
(9.0 ou superior):Desta vez,
readarray
armazena o conteúdo dosd
registros limitados por NUL em arquivos$arr
.IFS=$'\0'
não pode ser usado porquebash
nãobash
pode armazenar NULs em suas variáveis.Ou:
De qualquer forma, a maneira correta de obter a lista de arquivos no diretório de trabalho atual em uma matriz seria com:
Você só recorreria
ls --zero
se quisesse, por exemplo, que a lista fosse classificada por tamanho ou tempo de modificação, o que bash globs (ao contrário do zsh) não pode fazer.Como em:
new_to_old=( *.txt(Nom) )
readarray -td '' new_to_old < <(ls -td --zero -- *.txt)
four_largest=( *.txt(NOL[1,4]) )
readarray -td '' four_largest < <(ls -tdrS --zero -- *.txt | head -zn4)
Outra diferença entre
a=($(cmd))
ereadarray < <(cmd)
é o status de saída que no primeiro é o decmd
e no segundo o dereadarray
. Com versões recentes debash
, você pode obter o status de existênciacmd
no último comwait "$!"; cmd_status=$?
.¹ a
arr=( ... )
sintaxe vem de zsh (bash não tinha arrays até 2.0 em 1996), mas observe que em zsh, a substituição de comando, embora também esteja removendo novas linhas à direita e sujeita a -stripping, não descarta NULs (NUL está mesmo$IFS
no valor padrão de$IFS
lá) e não está sujeito a globbing como em outros shells do tipo Bourne, contribuindo para torná-lo um shell mais seguro em geral.²
readarray
akamapfile
não possui um modo de acréscimo, mas em versões recentes você pode informar o índice do primeiro elemento por onde começar a armazenar os elementos conforme-O
mostrado aqui. Para descobrir o índice do último elemento no bash (onde os arrays são esparsos como no ksh!), é terrivelmente difícil. Aqui, para anexar as linhas da saída decmd
to$arr
, em vez daquele código muito complicado, você também pode ler essas linhas em uma matriz temporária comreadarray -r tmp < <(cmd)
e anexar os elementos a$arr
witharr+=( "${tmp[@]}" )
. Observe também que se aarr
variável foi declarada como escalar ou associada, o comportamento variará entre essas abordagens.