14

So if I've got a variable

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

and echo it out

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

However, further down the script I need to reverse the order of this variable so it shows as something like

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

I tried using rev and it literally reversed everything so it came out as

echo "$VAR" | rev 001 09 08 07 06 05 04 03 02 01 
1
  • 2
    You may find some useful suggestions in answers to this similar question: How to read an IP address backwards? (just need to adjust for space separators in place of the periods) Commented Nov 3, 2016 at 12:30

8 Answers 8

22

On GNU systems, the reverse of cat is tac:

$ tac -s" " <<< "$VAR " # Please note the added final space. 100 90 80 70 60 50 40 30 20 10 
4
  • Cheers! That did the trick. I've still got a lot to learn Commented Nov 3, 2016 at 13:44
  • @JhonKugelman Without the echo, the output has an additional newline, equivalent to this: echo $'100 \n90 80 70 60 50 40 30 20 10' Commented Nov 3, 2016 at 23:16
  • With the current tac -s" " <<< "$VAR " version, it still inserts a leading blank line, adds a trailing space and omits the trailing newline character (as if printf '\n%s ' "$reversed_VAR" instead of expected printf '%s\n' "$reversed_VAR") Commented Nov 4, 2016 at 13:15
  • @don_crissti The original answer: echo $(tac -s" " <<< $VAR) does not have a trailing space because the $(…) removes trailing newlines and spaces if the IFS has them. My solution will be corrected soon, thanks. Commented Nov 4, 2016 at 14:45
7

For a short list, and in a variable, the shell itself is the fastest solution:

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 

Output:

100 90 80 70 60 50 40 30 20 10 

Note: the split operation in set -- $var is safe for the exact string used here, but will not be so if the string contains wildcard characters (*, ? or []). It may be avoided with set -f before the split, if needed. The same note is also valid for the following solutions (except where noted).

Or, if you want to set some other variable with the reverse list:

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

Or using only the positional parameters.

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" 

Or using only one variable (which may be var itself):
Note: This solution is not affected by globing nor IFS.

var="10 20 30 40 50 60 70 80 90 100" a=" $var" while [[ $a ]]; do printf '<%s>' "${a##* }" var="${a% *}" done 
0
2

awk to the rescue

$ 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 


with perl, courtesy How to read an IP address backwards? shared by @steeldriver

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


Or, with bash itself by converting the string to 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 
2

You can use bash features such as arrays to do it entirely in-process:

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

This should be about the fastest way to do it provided that $VAR isn't very very large.

1
  • That however doesn't extend to variables whose value contains wildcard characters (like VAR='+ * ?'). Use set -o noglob or set -f to fix that. More generally, it's a good idea to consider the value of $IFS and the state of globbing when using the split+glob operator. No that it makes little sense to use that operator in rev_a+=( ${a[$i]} ), rev_a+=( "${a[$i]}" ) would make a lot more sense. Commented Nov 4, 2016 at 10:32
2

zsh:

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

$=VAR splits $VAR on $IFS. (Oa) orders the resulting list in reverse array order. print -r -- (like in ksh), same as echo -E - (zsh specific) is a reliable versions of echo: prints its arguments as-is separated by space, terminated by newline.

If you want to split on space only, and not on whatever $IFS contains (space, tab, newline, nul by default), either assign space to $IFS, or use an explicit splitting like:

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

To sort in reverse numerical order:

$ 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 (so would also work with bash):

With shell builtin (except for printf in some shells) mechanisms only (better for variables with a short value):

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 (better for large variables, especially with bash, but up to the limit of the size of the arguments (or of a single argument)):

 awk ' BEGIN { n = split(ARGV[1], a); while (n) {printf "%s", sep a[n--]; sep = " "} print "" }' "$VAR" 
1
printf "%s\n" $var | tac | paste -sd' ' 100 90 80 70 60 50 40 30 20 10 
1

Use a bit of Python magic here:

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 

The way this works is fairly simple:

  • we refer to "$VAR" as second command line paramenter sys.argv[1] ( first parameter with -c option is always just that - -c itself. Notice the quoting around "$VAR" to prevent splitting the variable itself into individual words ( which hisi done by shell , not python )
  • Next we use .split() method, to break it into a list of strings
  • [::-1] part is just extended slice syntax to get reverse of the list
  • finally we join everything together back into one string with " ".join() and print out

But those who aren't big Python fans, we could use AWK to do essentially same thing: split, and print array in reverse:

 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 
3
  • 1
    or somewhat cleaner python3 -c 'import sys;print(*reversed(sys.argv[1:]))' $VAR Commented Nov 4, 2016 at 4:35
  • @iruvar, not cleaner, that would invoke the shell's split+glob operator without making sure $IFS contains the proper characters and without disabling the glob part. Commented Nov 4, 2016 at 13:09
  • @StéphaneChazelas, true enough - the python portion is still cleaner(IMHO) though. So, going back to "$VAR" yields python3 -c 'import sys;print(*reversed(sys.argv[1].split()))' "$VAR" Commented Nov 4, 2016 at 14:55
0

Inelegant POSIX software tools one-liner:

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

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.