Dividir string por caractere de espaço (problema estranho com base)

4

Eu preciso dividir a saída de ps , que é separada por espaçamento.

#!/bin/bash

A=$(ps -r -e -o pcpu=,comm= | head -1)
B=${A[0]}
C=${A[1]}
printf '%3s  %s\n' $B $(basename $C)

O resultado deve ser:

 42 bar

Em vez disso, obtenho:

usage: basename ...

Por que isso não funciona e, mais importante, como faço isso funcionar?

    
por forthrin 06.08.2018 / 09:48

4 respostas

5

Outros já notaram qual é o erro em seu código e sugeriram corretamente que, para seus dados iniciais de espaço reservado, um array seria a melhor opção de estrutura de dados, além de como dividir a string corretamente, etc.

Agora que sabemos qual é o seu comando real que você está analisando, podemos ser um pouco mais criativos com sugestões de melhoria.

O seguinte script pegará cada uma das linhas de saída do seu comando ps e a lerá como dois bits delimitados por espaço. O corpo do loop produz os bits lidos de maneiras diferentes:

#!/bin/bash

ps -r -e -o pcpu=,comm= |
while IFS=' ' read -r pcpu comm; do
    printf 'pcpu=%s,\tcomm=%s,\tbasename of comm=%s\n' \
        "$pcpu" "$comm" "${comm##*/}"
done

Aqui, comm manterá tudo após a primeira sequência de espaços na saída de ps (os espaços iniciais, antes da primeira coluna, serão aparados).

Você pode obviamente inserir seu head -n 1 como parte do pipeline inicial, se desejar.

Observe que em alguns shells, incluindo bash , o loop está sendo executado em um subshell, portanto, quaisquer variáveis criadas lá não estarão disponíveis após o término do pipeline. Existem duas soluções para isso em bash :

  1. Ative a opção lastpipe shell no script com shopt -s lastpipe ou
  2. Leia os dados no loop com uma substituição de processo:

    while IFS=' ' read ...
       # ...
    done < <( ps ... )
    

Exemplo de execução:

$ bash script.sh
pcpu=0.0,       comm=tmux,      basename of comm=tmux
pcpu=0.0,       comm=sh,        basename of comm=sh
pcpu=0.0,       comm=sh,        basename of comm=sh
pcpu=0.0,       comm=bash,      basename of comm=bash
pcpu=0.0,       comm=bash,      basename of comm=bash
pcpu=0.0,       comm=bash,      basename of comm=bash
pcpu=0.0,       comm=ps,        basename of comm=ps
pcpu=0.0,       comm=sh,        basename of comm=sh
    
por 06.08.2018 / 12:11
6

Para dividir uma string no caractere de espaço, você pode usar o operador split + glob. Isso é invocado na substituição de comandos ou na expansão de parâmetros, mas obviamente não ao armazenar em uma variável escalar que pode armazenar no máximo um valor.

 var=$(...)

É uma atribuição de variável escalar. Assim, atribuiríamos toda a saída a $var , o mesmo que ${var[0]} . Para uma atribuição de variável de matriz, é:

 var=($(...))

Agora, antes de invocá-lo, como sempre, você precisaria ajustá-lo:

 set -o noglob # disable the glob part which you don't want here
 IFS=' ' # split on space only
 var=($(some command)) # invoke it

Note que ele se divide em seqüências de espaços e os espaços iniciais e finais são ignorados.

Então, você não quer invocá-lo na expansão de $B nem $C nem $(basename...) , então é necessário citar as seguintes:

 printf '%3s  %s\n' "$B" "$(basename -- "$C")"

Além disso, não esqueça de -- para marcar o final das opções.

Como você esqueceu as aspas em torno de $C ( $C sendo vazio no seu caso, pois ${A[1]} nunca recebeu nenhum valor), basename não recebeu nenhum argumento em vez de receber esse argumento vazio reclamou sobre isso.

    
por 06.08.2018 / 10:33
3

defina array usando (). Se você usa aspas duplas, então estará tomando como string inteira.

Leia mais sobre a matriz

$ cat test.sh
#!/bin/bash

A=(42 /foo/bar)
B=${A[0]}
C=${A[1]}
printf '%3s  %s\n' $B $(basename $C)

$ bash test.sh
 42  bar
    
por 06.08.2018 / 09:54
0

Outra abordagem usando o awk:

 echo "42 /foo/bar" | awk '{n=split($2,b,"/"); print $1,b[n]}'
    
por 06.08.2018 / 11:27

Tags