Invertendo o conteúdo de uma variável por palavras

13

Então, se eu tenho uma variável

VAR='10 20 30 40 50 60 70 80 90 100'

e ecoar

echo "$VAR"
10 20 30 40 50 60 70 80 90 100

No entanto, mais abaixo, no script, preciso reverter a ordem dessa variável, para que ela seja exibida como algo como

echo "$VAR" | <code to reverse it>
100 90 80 70 60 50 40 30 20 10

Eu tentei usar rev e literalmente reverteu tudo, então saiu

echo "$VAR" | rev
001 09 08 07 06 05 04 03 02 01
Alistair Hardy
fonte
2
Você pode encontrar algumas sugestões úteis em respostas a essa pergunta semelhante: Como ler um endereço IP ao contrário? (só precisa ajustar para separadores de espaço no lugar dos períodos)
steeldriver

Respostas:

21

Nos sistemas GNU, o reverso de cat é tac:

$ tac -s" " <<< "$VAR "            # Please note the added final space.
100 90 80 70 60 50 40 30 20 10
Ipor Sircer
fonte
Felicidades! Isso fez o truque. Eu ainda tenho muito a aprender
Alistair Hardy
@JhonKugelman Sem o eco, a saída tem uma nova linha adicional, equivalente a esta:echo $'100 \n90 80 70 60 50 40 30 20 10'
sorontar
Com a atual tac -s" " <<< "$VAR "versão, ainda insere uma linha em branco inicial, adiciona um espaço à direita e omite o caráter de fuga de nova linha (como se printf '\n%s ' "$reversed_VAR"em vez de esperar printf '%s\n' "$reversed_VAR")
Stéphane Chazelas
@don_crissti A resposta original: echo $(tac -s" " <<< $VAR)não possui um espaço à direita porque $(…)remove novas linhas e espaços à direita se o IFS os possuir. Minha solução será corrigida em breve, obrigado.
sorontar
6

Para uma lista curta e em uma variável, o próprio shell é a solução mais rápida:

var="10 20 30 40 50 60 70 80 90 100"
set -- $var; unset a 
for ((i=$#;i>0;i--)); do
    printf '%s%s' "${a:+ }" "${!i}"
    a=1
done
echo

Resultado:

100 90 80 70 60 50 40 30 20 10 

Nota: a operação de divisão em set -- $varé segura para a sequência exata usada aqui, mas não será assim se a sequência contiver caracteres curinga ( *, ?ou []). Pode ser evitado comset -f antes da divisão, se necessário. A mesma nota também é válida para as seguintes soluções (exceto onde indicado).

Ou, se você deseja definir outra variável com a lista reversa:

var="10 20 30 40 50 60 70 80 90 100"
set -- $var
for i; do out="$i${out:+ }$out"; done
echo "$out"

Ou usando apenas os parâmetros posicionais.

var="10 20 30 40 50 60 70 80 90 100"
set -- $var
a=''
for    i
do     set -- "$i${a:+ $@}"
       a=1
done
echo "$1"

Ou usando apenas uma variável (que pode ser a própria var):
Nota: Esta solução não é afetada pela globing nem pelo IFS.

var="10 20 30 40 50 60 70 80 90 100"

a=" $var"
while [[ $a ]]; do
    printf '<%s>' "${a##* }"
    var="${a% *}"
done
sorontar
fonte
2

awk para o resgate

$ var='10 20 30 40 50 60 70 80 90 100'

$ echo "$var" | awk '{for(i=NF; i>0; i--) printf i==1 ? $i"\n" : $i" "}'
100 90 80 70 60 50 40 30 20 10


com perl, cortesia Como ler um endereço IP ao contrário? compartilhado por @steeldriver

$ echo "$var" | perl -lane '$,=" "; print reverse @F'
100 90 80 70 60 50 40 30 20 10


Ou, bashconvertendo a string em array

$ arr=($var)    
$ for ((i=${#arr[@]}-1; i>=0; i--)); do printf "${arr[i]} "; done; echo
100 90 80 70 60 50 40 30 20 10 
Sundeep
fonte
2

Você pode usar recursos do bash, como matrizes, para fazê-lo totalmente em processo:

#!/bin/bash
VAR="10 20 30 40 50 60 70 80 90 100"
a=($VAR); rev_a=()
for ((i=${#a[@]}-1;i>=0;i--)); do rev_a+=( ${a[$i]} ); done; 
RVAR=${rev_a[*]}; 

Essa deve ser a maneira mais rápida de fazer isso, desde que o $ VAR não seja muito grande.

PSkocik
fonte
Isso, no entanto, não se estende a variáveis ​​cujo valor contém caracteres curinga (como VAR='+ * ?'). Use set -o noglobou set -fpara consertar isso. De maneira mais geral, é uma boa ideia considerar o valor $IFSe o estado do globbing ao usar o operador split + glob. Não que faça pouco sentido usar esse operador rev_a+=( ${a[$i]} ), rev_a+=( "${a[$i]}" )faria muito mais sentido.
Stéphane Chazelas 4/11
1
printf "%s\n" $var | tac | paste -sd' '
100 90 80 70 60 50 40 30 20 10
Costas
fonte
1

Use um pouco de magia Python aqui:

bash-4.3$ VAR='10 20 30 40 50 60 70 80 90 100'
bash-4.3$ python -c 'import sys;print " ".join(sys.argv[1].split()[::-1])'  "$VAR" 
100 90 80 70 60 50 40 30 20 10

A maneira como isso funciona é bastante simples:

  • nós nos referimos "$VAR"como o segundo parâmetro de linha de comando sys.argv[1](o primeiro parâmetro com -copção é sempre exatamente isso - -cele próprio. Observe as aspas "$VAR"para evitar a divisão da variável em palavras individuais (o que é feito por shell, não por python)
  • Em seguida, usamos o .split()método, para dividi-lo em uma lista de strings
  • [::-1]parte é apenas sintaxe de fatia estendida para reverter a lista
  • finalmente, juntamos tudo de volta em uma sequência " ".join()e imprimimos

Mas aqueles que não são grandes fãs de Python, poderíamos AWKfazer essencialmente a mesma coisa: dividir e imprimir array ao contrário:

 echo "$VAR" | awk '{split($0,a);x=length(a);while(x>-1){printf "%s ",a[x];x--};print ""}'
100 90 80 70 60 50 40 30 20 10 
Sergiy Kolodyazhnyy
fonte
1
ou algo mais limpopython3 -c 'import sys;print(*reversed(sys.argv[1:]))' $VAR
iruvar 4/16
@iruvar, não mais limpo , que chamaria o operador split + glob do shell sem ter certeza de que $IFScontém os caracteres adequados e sem desativar a parte glob.
Stéphane Chazelas 4/11
@ StéphaneChazelas, é verdade - a parte python ainda é mais limpa (IMHO). Então, voltando ao "$VAR"rendimentopython3 -c 'import sys;print(*reversed(sys.argv[1].split()))' "$VAR"
Iruvar
1

zsh:

print -r -- ${(Oa)=VAR}

$=VARdivide $VARem $IFS. (Oa)ordena a lista resultante na ordem inversa da matriz. print -r --(como em ksh), o mesmo que echo -E -( zshespecífico) é uma versão confiável deecho : imprime seus argumentos como estão separados por espaço, finalizados por nova linha.

Se você deseja dividir somente no espaço, e não no $IFSconteúdo (espaço, tabulação, nova linha, nul por padrão), atribua espaço $IFSou use uma divisão explícita como:

print -r -- ${(Oas: :)VAR}

Para classificar em ordem numérica inversa:

$ VAR='50 10 20 90 100 30 60 40 70 80'
$ print -r -- ${(nOn)=VAR}
100 90 80 70 60 50 40 30 20 10

POSIXly (assim também funcionaria bash):

printfSomente com mecanismos embutidos no shell (exceto em alguns shells) (melhor para variáveis ​​com um valor curto):

unset -v IFS # restore IFS to its default value of spc, tab, nl
set -o noglob # disable glob
set -- $VAR # use the split+glob operator to assign the words to $1, $2...

reversed_VAR= sep=
for i do
  reversed_VAR=$i$sep$reversed_VAR
  sep=' '
done
printf '%s\n' "$reversed_VAR"

With awk(melhor para variáveis ​​grandes, especialmente com bash, mas até o limite do tamanho dos argumentos (ou de um único argumento)):

 awk '
   BEGIN {
     n = split(ARGV[1], a);
     while (n) {printf "%s", sep a[n--]; sep = " "}
     print ""
   }' "$VAR"
Stéphane Chazelas
fonte
0

Ferramentas de software POSIX deselegantes , uma linha:

echo `echo $VAR | tr ' ' '\n' | sort -nr`
agc
fonte