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?
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityI 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/'
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}"
?
– user79743
Aug 10 '16 at 0:17
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
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"}'
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
}
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.