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?
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 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 ..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/' 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"}' 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 orderj:.:: 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'.
IFS="."; set -f; set -- $string$IFS; for i; do rev="$i$IFS$rev"; done; printf '%s\n' "${rev%$IFS}"? 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). 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.
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 }