Bash - Matrizes multidimensionais e extraindo variáveis da saída

7

Estou tentando fazer algo simples, mas não sei como alcançar meu objetivo aqui.

Eu estou tentando extrair os valores: USER, TTY e FROM que são dados pelo comando w no console. No bash, estou tentando pegar essa saída e obter esses valores em uma matriz multidimensional (ou apenas uma matriz com um delimitador de espaço).

#!/bin/bash
w|awk '{if(NR > 2) print $1,$2,$3}' | while read line
do
     USERS+=("$line")
     echo ${#USERS[@]}
done
echo ${#USERS[@]}

Encontrei meu caminho até o ponto de ler os valores por linha em um único array, mas não consigo tirar o valor do array USERS do escopo do loop while. Imprime os valores 1,2,3,4 e depois 0 após o loop. Cada exemplo que eu leio eles usam a variável fora do escopo perfeitamente bem, mas eu não consigo.

    
por russ 07.06.2013 / 23:12

4 respostas

5

Seu principal problema é que o último comando em um pipeline é executado em um subshell, como todos os outros comandos no pipeline. Este é o caso da maioria dos shells. ATT ksh e zsh são exceções: eles executam o último comando do pipeline no shell pai.

Desde o bash 4.2, você pode dizer ao bash para se comportar como ksh e zsh, definindo o lastpipe opção .

#!/bin/bash
USERS=()
shopt -s lastpipe
w | awk '{if(NR > 2) print $1,$2,$3}' | while read line; do
  USERS+=("$line")
done
echo ${#USERS[@]}

Como alternativa, você pode usar a substituição de processo em vez de um canal. que o comando read é executado no processo principal do shell.

#!/bin/bash
USERS=()
while read line; do
  USERS+=("$line")
done < <(w | awk '{if(NR > 2) print $1,$2,$3}')
echo ${#USERS[@]}

Como alternativa, você pode usar a abordagem portável, que funciona em shells que não têm comportamento de processo nem em ksh / zsh, como Bourne, dash e pdksh. (Você ainda precisa de (pd) ksh, bash ou zsh para matrizes.) Execute tudo o que requer os dados do pipeline dentro do pipeline.

#!/bin/bash
USERS=()
shopt -s lastpipe
w | awk '{if(NR > 2) print $1,$2,$3}' | {
  while read line; do
    USERS+=("$line")
  done
  echo ${#USERS[@]}
}
    
por 08.06.2013 / 02:07
4

Com shopt -s lastpipe , você pode pegar o último comando de um pipeline no ambiente atual do shell. Isso resolve o seu problema. Eu acho que esse recurso não foi sempre no bash, então evitá-lo se você quiser código amplamente compatível.

A alternativa compatível:

export_array="$(w | awk '{if(NR > 2) print $1,$2,$3}' | 
  { USERS=(); while read line; do
      USERS[]="$line"
    done
    declare -p USERS; } )"
eval "$export_array"
    
por 08.06.2013 / 01:15
4

Matrizes de bash são unidimensionais. Se você deseja manter valores separados ordenados para cada linha, uma solução é usar matrizes associativas. Um exemplo bruto:

Tenha também cuidado com esses nomes de variáveis maiúsculas, pois eles podem entrar em conflito com variáveis de ambiente.

#!/bin/bash

declare -i i=0 j=0
declare -A w

while read -r user tty from _;do
    ((++i > 2)) || continue
    w["$j.user"]="$user"
    w["$j.tty"]="$tty"
    w["$j.from"]="$from"
    ((++j))
done < <(w)

for ((i = 0; i < j; ++i)); do
    printf "entry %-2d {\n  %-5s: %s\n  %-5s: %s\n  %-5s: %s\n}\n" \
    "$i" \
    "user" "${w[$i.user]}" \
    "tty"  "${w[$i.tty]}" \
    "from" "${w[$i.from]}"
done
    
por 08.06.2013 / 01:31
3

para armazenamento em matrizes bash, usar um delimitador diferente do espaço é geralmente mais simples.

    readarray -s2 -t my_w_array < <(w | awk '{ print $1":"$2":"$3 }')

você pode dividi-lo ao imprimi-lo, como:

    printf '%s\n' "${my_w_array[@]//:/ }"
    
por 08.06.2013 / 01:27

Tags