Invertir los contenidos de una variable por palabras

13

Entonces si tengo una variable

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

y repetirlo

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

Sin embargo, más abajo en el script, necesito invertir el orden de esta variable para que se muestre como algo así

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

Intenté usar rev y literalmente revirtió todo, así que salió como

echo "$VAR" | rev
001 09 08 07 06 05 04 03 02 01
Alistair Hardy
fuente
2
Puede encontrar algunas sugerencias útiles en las respuestas a esta pregunta similar: ¿Cómo leer una dirección IP al revés? (solo es necesario ajustar los separadores de espacio en lugar de los períodos)
steeldriver el

Respuestas:

21

En los sistemas GNU, el reverso de cat es tac:

$ tac -s" " <<< "$VAR "            # Please note the added final space.
100 90 80 70 60 50 40 30 20 10
Ipor Sircer
fuente
¡Salud! Eso hizo el truco. Todavía tengo mucho que aprender
Alistair Hardy
@JhonKugelman Sin el eco, la salida tiene una nueva línea adicional, equivalente a esto:echo $'100 \n90 80 70 60 50 40 30 20 10'
sorontar
Con la tac -s" " <<< "$VAR "versión actual , todavía inserta una línea en blanco inicial, agrega un espacio final y omite el carácter de nueva línea final (como si en printf '\n%s ' "$reversed_VAR"lugar de lo esperado printf '%s\n' "$reversed_VAR")
Stéphane Chazelas
@don_crissti La respuesta original: echo $(tac -s" " <<< $VAR)no tiene un espacio final porque $(…)elimina las nuevas líneas y espacios finales si el IFS los tiene. Mi solución será corregida pronto, gracias.
sorontar
6

Para una lista corta, y en una variable, el shell en sí es la solución más 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

Salida:

100 90 80 70 60 50 40 30 20 10 

Nota: la operación de división en set -- $vares segura para la cadena exacta utilizada aquí, pero no lo será si la cadena contiene caracteres comodín ( *, ?o []). Se puede evitar set -fantes de la división, si es necesario. La misma nota también es válida para las siguientes soluciones (excepto donde se indique).

O, si desea establecer alguna otra variable con la lista inversa:

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

O usando solo los parámetros posicionales.

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"

O usando solo una variable (que puede ser var):
Nota: Esta solución no se ve afectada por el gloging ni IFS.

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

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

awk al rescate

$ 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


con perl, cortesía ¿Cómo leer una dirección IP al revés? compartido por @steeldriver

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


O, consigo bashmismo al convertir la cadena en matriz

$ 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
fuente
2

Puede usar funciones bash como matrices para hacerlo completamente en proceso:

#!/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[*]}; 

Esta debería ser la forma más rápida de hacerlo, siempre que $ VAR no sea muy grande.

PSkocik
fuente
Sin embargo, eso no se extiende a las variables cuyo valor contiene caracteres comodín (como VAR='+ * ?'). Use set -o noglobo set -fpara arreglar eso. En términos más generales, es una buena idea considerar el valor $IFSy el estado de globing cuando se usa el operador split + glob. No es que tenga poco sentido usar ese operador rev_a+=( ${a[$i]} ), rev_a+=( "${a[$i]}" )tendría mucho más sentido.
Stéphane Chazelas
1
printf "%s\n" $var | tac | paste -sd' '
100 90 80 70 60 50 40 30 20 10
Costas
fuente
1

Use un poco de magia Python aquí:

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

La forma en que esto funciona es bastante simple:

  • nos referimos "$VAR"como segundo sys.argv[1]parámetro de la línea de comando (el primer parámetro con -copción siempre es solo eso, en -csí mismo). Observe la cita "$VAR"para evitar dividir la variable en palabras individuales (que hisi hizo por shell, no por python)
  • Luego usamos el .split()método, para dividirlo en una lista de cadenas
  • [::-1]parte es solo la sintaxis de corte extendida para obtener el reverso de la lista
  • finalmente unimos todo de nuevo en una sola cadena " ".join()e imprimimos

Pero aquellos que no son grandes fanáticos de Python, podríamos AWKhacer esencialmente lo mismo: dividir e imprimir matriz en reversa:

 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
fuente
1
o algo más limpiopython3 -c 'import sys;print(*reversed(sys.argv[1:]))' $VAR
iruvar el
@iruvar, no limpiador , que invocaría al operador split + glob del shell sin asegurarse de que $IFScontiene los caracteres adecuados y sin desactivar la parte glob.
Stéphane Chazelas
@ StéphaneChazelas, es cierto: la parte de Python aún está más limpia (en mi humilde opinión). Entonces, volviendo a los "$VAR"rendimientospython3 -c 'import sys;print(*reversed(sys.argv[1].split()))' "$VAR"
iruvar el
1

zsh:

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

$=VARse divide $VARen $IFS. (Oa)ordena la lista resultante en orden de matriz inversa. print -r --(como en ksh), lo mismo que echo -E -( zshespecífico) es una versión confiable deecho : imprime sus argumentos tal como están separados por espacio, terminados por nueva línea.

Si desea dividir solo en el espacio, y no en lo que sea que $IFScontenga (espacio, tabulación, nueva línea, nul por defecto), asigne espacio $IFSo use una división explícita como:

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

Para ordenar en orden numérico inverso:

$ 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 (también funcionaría con bash):

Con printfmecanismos incorporados de shell (excepto en algunos shells) solamente (mejor para variables con un valor corto):

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"

Con awk (mejor para variables grandes, especialmente con bash, pero hasta el límite del tamaño de los argumentos (o de un solo argumento)):

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

Herramientas de software POSIX poco elegantes de una sola línea:

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