Pode ser explicado; a diferença no comportamento do array entre o uso de array = $ (comando) e array = ($ (comando))?

4

Eu entendo a substituição de comandos. Eu entendo subshells. Eu não entendo porque usar um subshell muda a estrutura do meu array.

Dada esta saída de comando: (o uso do comando openstack não se destina a ser relevante)

bash$ floating ip list -c 'Floating IP Address' -f value
172.25.250.106
172.25.250.107
172.25.250.101

Tente capturar em uma matriz, mas todos os endereços acabam no elemento 0:

bash$ float=$( openstack floating ip list -c 'Floating IP Address' -f value )
bash$ echo ${float[@]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[@]}
1
bash$ echo ${float[0]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[0]}
44

Toda a saída foi capturada como uma string e não analisada em elementos. Eu esperava que cada palavra se tornasse um elemento. Quando eu repito isso para garantir que cada endereço IP seja citado (usando -f csv em vez de valor -f), os resultados são os mesmos.

Em seguida, eu coloco a substituição do comando em um subshell:

bash$ unset float
bash$ float=( $( openstack floating ip list -c 'Floating IP Address' -f value ) )
bash$ echo ${float[@]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[@]}
3
echo ${float[0]}
172.25.250.106
echo ${#float[0]}
14

Esse foi o comportamento que eu esperava originalmente. Também percebo que a construção da matriz usando uma instrução de leitura funcionou conforme o esperado:

bash$ unset float
bash$ read -a float <<< $( openstack floating ip list -c 'Floating IP Address' -f value )
bash$ echo ${float[@]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[@]}
3
echo ${float[0]}
172.25.250.106
echo ${#float[0]}
14

Eu gostaria de ver o trabalho original de substituição de comandos. Perguntando se eu deveria ter definido um separador de campo primeiro ou o que mais eu estava perdendo. Estou tentando entender o que causa a diferença de comportamento.

    
por Philip Sweany 18.06.2017 / 00:36

3 respostas

4
float=$( openstack floating ip list -c 'Floating IP Address' -f value )

Isso cria uma variável string , não uma variável de matriz. A string é a saída do comando, menos qualquer nova linha no final.

Se você tentar usar uma variável de string como uma matriz, ela será tratada como uma matriz de elemento único com o valor da string na posição 0.

float=( $( openstack floating ip list -c 'Floating IP Address' -f value ) )

Isso não “coloca a substituição do comando em um subshell”. A própria substituição do comando $(…) cria um subshell. Os parênteses em torno dele não criam outro subnível: eles criam um array. A matriz contém o conteúdo da lista de palavras resultante da obtenção da saída do comando, da remoção de novas linhas à direita, da divisão em uma lista de palavras separadas por espaços em branco e da substituição de qualquer elemento dessa lista que contenha caracteres curinga que correspondam a um ou mais arquivos lista de nomes de arquivos correspondentes.

Os parênteses criam um subshell quando estão em um local onde um comando é esperado. Em var=(…) , o que é esperado imediatamente após o sinal de igual não é um comando, mas um valor para atribuição. Nesse contexto, os parênteses indicam que o valor é uma matriz.

    
por 18.06.2017 / 00:54
2

Os parênteses em var=( some things ) não marcam um subshell, eles são parte do sintaxe de atribuição de matriz . Então, sem eles, você está atribuindo a uma variável regular (escalar). O lado direito de uma atribuição regular não passa pela divisão de palavras, portanto, var=$(echo foo bar) colocará a string foo bar em var com os espaços. Por outro lado, a atribuição da matriz usa várias palavras, portanto, arr=(foo bar) e arr=( $(echo foo bar) ) fornecem uma matriz de dois elementos.

    
por 18.06.2017 / 00:53
2
float=$( openstack floating ip list -c 'Floating IP Address' -f value )

Isso é substituição de comandos dentro de um atribuição de variável regular , não uma atribuição de matriz. Todas as atribuições de matrizes compostas têm o formulário x=( ... ) , como você usou abaixo. Não há subshell aqui (diferente de brevemente como o contexto de execução do comando substituído).

Quando você usa uma substituição de comando, ela se comporta da mesma maneira que uma variável com esse conteúdo nessa posição e, assim, analogamente com float=$x aqui, mas também com ls $x ou foo=($x) . A divisão de palavras é executada no resultado da expansão, que separa o valor em argumentos separados em qualquer caractere na variável IFS .

Você pode suprimir a divisão de palavras citando a expansão "$(...)" .

Se você quiser criar uma matriz de palavras divididas, será necessário both para criar uma matriz e dividir as palavras: isso significa uma atribuição de matriz foo=(...) e um parâmetro ou substituição de comando $... combinado, como no seu segundo caso:

float=( $( openstack floating ip list -c 'Floating IP Address' -f value ) )

O float=(...) faz uma matriz e os elementos vêm da divisão de palavras executada durante a substituição do comando $(...) .

O que pode ser confuso é que o Bash converte automaticamente não arrays em arrays singleton quando você os usa como um, então parece que você conseguiu um array, mas de um único item.

echo ${float[0]}
echo ${#float[@]}

Isso é documentado de maneira muito sutil :

Any reference to a variable using a valid subscript is legal, and bash will create an array if necessary.

Você verá isso com mais clareza se usar outro índice:

float[1]=abc
echo ${#float[@]} # => 2

O processo de conversão usa apenas o valor existente da variável, se houver, como o item no índice 0 da matriz.

    
por 18.06.2017 / 00:57