Inverser le contenu d'une variable par des mots


13

Donc, si j'ai une variable

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

et l'écho

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

Cependant, plus bas dans le script, j'ai besoin d'inverser l'ordre de cette variable pour qu'elle s'affiche comme quelque chose comme

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

J'ai essayé d'utiliser rev et il a littéralement inversé tout ce qui est sorti

echo "$VAR" | rev
001 09 08 07 06 05 04 03 02 01

2
Vous pouvez trouver des suggestions utiles dans les réponses à cette question similaire: Comment lire une adresse IP à l'envers? (il suffit de régler les séparateurs d'espace à la place des périodes)
Steeldriver

Réponses:


21

Sur les systèmes GNU, l'inverse de cat est tac:

$ tac -s" " <<< "$VAR "            # Please note the added final space.
100 90 80 70 60 50 40 30 20 10

À votre santé! Cela a fait l'affaire. J'ai encore beaucoup à apprendre
Alistair Hardy

@JhonKugelman Sans l'écho, la sortie a une nouvelle ligne supplémentaire, équivalente à ceci:echo $'100 \n90 80 70 60 50 40 30 20 10'
sorontar

Avec la tac -s" " <<< "$VAR "version actuelle , il insère toujours une ligne vide de tête, ajoute un espace de fin et omet le caractère de fin de ligne (comme si printf '\n%s ' "$reversed_VAR"au lieu de prévu printf '%s\n' "$reversed_VAR")
Stéphane Chazelas

@don_crissti La réponse originale: echo $(tac -s" " <<< $VAR)n'a pas d'espace de fin car le $(…)supprime les nouvelles lignes et les espaces de fin si l'IFS en a. Ma solution sera bientôt corrigée, merci.
sorontar

6

Pour une courte liste, et dans une variable, le shell lui-même est la solution la plus rapide:

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

Production:

100 90 80 70 60 50 40 30 20 10 

Remarque: l'opération de division dans set -- $varest sûre pour la chaîne exacte utilisée ici, mais ne le sera pas si la chaîne contient des caractères génériques ( *, ?ou []). Il peut être évité avec set -favant la séparation, si nécessaire. La même note est également valable pour les solutions suivantes (sauf indication contraire).

Ou, si vous souhaitez définir une autre variable avec la liste inversée:

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

Ou en utilisant uniquement les paramètres de position.

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 en utilisant une seule variable (qui peut être var elle-même):
Remarque: Cette solution n'est pas affectée par la globalisation ni par IFS.

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

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

2

awk à la rescousse

$ 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


avec perl, courtoisie Comment lire une adresse IP à l'envers? partagé par @steeldriver

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


Ou, avec bashlui-même en convertissant la chaîne en tableau

$ 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 

2

Vous pouvez utiliser des fonctionnalités bash telles que des tableaux pour le faire entièrement en cours:

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

Cela devrait être le moyen le plus rapide de le faire à condition que $ VAR ne soit pas très très grand.


Cependant, cela ne s'étend pas aux variables dont la valeur contient des caractères génériques (comme VAR='+ * ?'). Utilisez set -o noglobou set -fpour corriger cela. Plus généralement, c'est une bonne idée de considérer la valeur $IFSet l'état de la globalisation lors de l'utilisation de l'opérateur split + glob. Non, cela n'a pas de sens d'utiliser cet opérateur rev_a+=( ${a[$i]} ), rev_a+=( "${a[$i]}" )aurait beaucoup plus de sens.
Stéphane Chazelas

1
printf "%s\n" $var | tac | paste -sd' '
100 90 80 70 60 50 40 30 20 10

1

Utilisez un peu de magie Python ici:

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 façon dont cela fonctionne est assez simple:

  • nous appelons "$VAR"le second sys.argv[1]paramètre de ligne de commande (le premier paramètre avec -coption est toujours juste cela - -clui - même. Remarquez les guillemets "$VAR"pour éviter de diviser la variable elle-même en mots individuels (ce que hisi fait par shell, pas par python)
  • Ensuite, nous utilisons la .split()méthode, pour le diviser en une liste de chaînes
  • [::-1]une partie est simplement une syntaxe de tranche étendue pour obtenir l'inverse de la liste
  • enfin, nous joignons tout ensemble en une seule chaîne avec " ".join()et imprimons

Mais ceux qui ne sont pas de grands fans de Python, nous pourrions utiliser AWKessentiellement la même chose: diviser et imprimer le tableau à l'envers:

 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 

1
ou un peu plus proprepython3 -c 'import sys;print(*reversed(sys.argv[1:]))' $VAR
iruvar

@iruvar, pas plus propre , qui invoquerait l'opérateur split + glob du shell sans s'assurer qu'il $IFScontient les caractères appropriés et sans désactiver la partie glob.
Stéphane Chazelas

@ StéphaneChazelas, c'est vrai - la partie python est toujours plus propre (à mon humble avis). Donc, revenons aux "$VAR"rendementspython3 -c 'import sys;print(*reversed(sys.argv[1].split()))' "$VAR"
iruvar

1

zsh:

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

$=VARse divise $VARsur $IFS. (Oa)ordonne la liste résultante dans l'ordre inverse des tableaux. print -r --(comme dans ksh), de même que echo -E -( zshspécifique) est une version fiable deecho : affiche ses arguments comme-est séparé par un espace, terminé par une nouvelle ligne.

Si vous souhaitez diviser uniquement sur l'espace, et non sur tout ce qui $IFScontient (espace, tabulation, nouvelle ligne, nul par défaut), affectez de l'espace à $IFS, ou utilisez un fractionnement explicite comme:

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

Pour trier dans l'ordre numérique inverse:

$ 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 (cela fonctionnerait également avec bash):

Avec les mécanismes internes du shell (sauf pour printfcertains shells) uniquement (mieux pour les variables avec une valeur courte):

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"

Avec awk(mieux pour les grandes variables, surtout avec bash, mais jusqu'à la limite de la taille des arguments (ou d'un seul argument)):

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

0

Outils logiciels POSIX inélégants monoligne :

echo `echo $VAR | tr ' ' '\n' | sort -nr`
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.