14

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?

21
  • 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
    
  • 2
    Bah, I was just going to post the Perl solution :) – ilkkachu Aug 9 '16 at 12:55
10

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 ..
5

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/'
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'.

  • Whats wrong with IFS="."; set -f; set -- $string$IFS; for i; do rev="$i$IFS$rev"; done; printf '%s\n' "${rev%$IFS}"? – user79743 Aug 10 '16 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). – Stéphane Chazelas Aug 10 '16 at 6:52
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
}
2

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"}'
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.