20

I have an input string like:

arg1.arg2.arg3.arg4.arg5 

The output I want is:

arg5.arg4.arg3.arg2.arg1 

It's not always 5 arg's, could be 2 to 10.

How can I do this in a bash script?

8 Answers 8

29
  • Using combination of tr+tac+paste

    $ tr '.' $'\n' <<< 'arg1.arg2.arg3.arg4.arg5' | tac | paste -s -d '.' arg5.arg4.arg3.arg2.arg1 
  • If you still prefer bash, you could do in this way,

    IFS=. read -ra line <<< "arg1.arg2.arg3.arg4." let x=${#line[@]}-1; while [ "$x" -ge 0 ]; do echo -n "${line[$x]}."; let x--; done 
  • Using perl,

    $ echo 'arg1.arg2.arg3.arg4.arg5' | perl -lne 'print join ".", reverse split/\./;' arg5.arg4.arg3.arg2.arg1 
1
  • 2
    Bah, I was just going to post the Perl solution :) Commented Aug 9, 2016 at 12:55
11

If you do not mind a tiny bit of python:

".".join(s.split(".")[::-1]) 

Example:

$ python3 -c 's="arg1.arg2.arg3.arg4.arg5"; print(".".join(s.split(".")[::-1]))' arg5.arg4.arg3.arg2.arg1 
  • s.split(".") generates a list containing . separated substrings, [::-1] reverses the list and ".".join() joins the components of the reversed list with ..
6

You can do it with a single sed invocation:

sed -E 'H;g;:t;s/(.*)(\n)(.*)(\.)(.*)/\1\4\5\2\3/;tt;s/\.(.*)\n/\1./' 

this uses the hold buffer to get a leading newline in the pattern space and uses it to do permutations until the newline is no longer followed by any dot at which point it removes the leading dot from the pattern space and replaces the newline with a dot.
With BRE and a slightly different regex:

sed 'H g :t s/\(.*\)\(\n\)\(.*\)\(\.\)\(.*\)/\1\4\5\2\3/ tt s/\(\.\)\(.*\)\n/\2\1/' 

If the input consists of more than one line and you want to reverse the order on each line:

sed -E 'G;:t;s/(.*)(\.)(.*)(\n)(.*)/\1\4\5\2\3/;tt;s/(.*)\n(\.)(.*)/\3\2\1/' 
5

Didn't see an awk answer, so here's one:

 echo 'arg1.arg2.arg3' | awk -F. '{for (i=NF; i>0; --i) printf "%s%s", (i<NF ? "." : ""), $i; printf "\n"}' 
3

SED solution:

sed 's/\./\n/g' yourFile | tac | sed ':a; $!{N;ba};s/\n/./g' 
3

With zsh:

$ string=arg1.arg2.arg3.arg4.arg5 $ printf '%s\n' ${(j:.:)${(Oas:.:)string}} arg5.arg4.arg3.arg2.arg1 

To preserve empty elements:

$ string=arg1.arg2.arg3.arg4..arg5. $ printf '%s\n' ${(j:.:)"${(@Oas:.:)string}"} .arg5..arg4.arg3.arg2.arg1 
  • s:.:: split on .
  • Oa: sort array in reverse array indice order
  • j:.:: join on ..
  • @: do "$@"-like expansion when in double quotes so the expansion doesn't undergo empty-removal.

The POSIX (or bash) equivalent could be something like:

string=arg1.arg2.arg3.arg4..arg5. IFS=.; set -f set -- $string$IFS # split string into "$@" array unset pass for i do # reverse "$@" array set -- "$i" ${pass+"$@"} pass=1 done printf '%s\n' "$*" # "$*" to join the array elements with the first # character of $IFS 

(note that zsh (in sh emulation), yash and some pdksh-based shells are not POSIX in that regard in that they treat $IFS as a field separator instead of field delimiter)

Where it differs from some of the other solutions given here is that it doesn't treat the newline character (and in the case of zsh, the NUL one either) specially, that is $'ab.cd\nef.gh' would be reversed to $'gh.cd\nef.ab', not $'cd.ab\ngh.ef' or $'gh.ef.cd.ab'.

2
  • Whats wrong with IFS="."; set -f; set -- $string$IFS; for i; do rev="$i$IFS$rev"; done; printf '%s\n' "${rev%$IFS}"? Commented Aug 10, 2016 at 0:17
  • @BinaryZebra Nothing (apart maybe from the for i; do which is not POSIX (yet) even if it's supported by all the POSIX shells that I know). It's just that that solution is a direct translation of the zsh one (split into array, reverse array, join array elements) but using POSIX-only operators. (I've changed the string=$string$IFS; set -- $string to set -- $string$IFS as that seems to work consistently across shells, thanks). Commented Aug 10, 2016 at 6:52
3

Pure Bash, requires trailing period on input:

echo 'foo.bar.baz.' | ( while read -d . f;do g="$f${g+.}$g" ;done;echo "$g" ) 

Use printf %s "$g" if any of the dotted elements could start with a hyphen.

1
  • That's a neat one-liner! Commented May 24, 2022 at 5:38
2

I see Rahul already posted a native bash solution (among others), which is a shorter version of the first one I came up with:

function reversedots1() ( IFS=. read -a array <<<"$1" new=${array[-1]} for((i=${#array[*]} - 2; i >= 0; i--)) do new=${new}.${array[i]} done printf '%s\n' "$new" ) 

but I couldn't stop there, and felt the need to write a recursive bash-centric solution:

function reversedots2() { if [[ $1 =~ ^([^.]*)\.(.*)$ ]] then printf %s $(reversedots2 "${BASH_REMATCH[2]}") . "${BASH_REMATCH[1]}" else printf %s "$1" fi } 

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.