I have a shell script that starts unit tests for modules. I need the name of the module in all lowercase and with the first character uppercase. So far I have been doing it like this:
#!/bin/sh -x
# z.B. getbrowser
strModuleToTest=$1
# g
strModuleToTestUppercaseFirstletter=${strModuleToTest:0:1}
# etbrowser
strModuleToTestUppercaseLastletters=${strModuleToTest:1}
# g -> G
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/a/A}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/b/B}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/c/C}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/d/D}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/e/E}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/f/F}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/g/G}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/h/H}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/i/I}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/j/J}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/k/K}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/l/L}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/m/M}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/n/N}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/o/O}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/p/P}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/q/Q}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/r/R}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/s/S}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/t/T}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/u/U}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/v/V}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/w/W}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/x/X}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/y/Y}"
strModuleToTestUppercaseFirstletter="${strModuleToTestUppercaseFirstletter/z/Z}"
# Getbrowser
strModuleToTestUppercase=$strModuleToTestUppercaseFirstletter""$strModuleToTestUppercaseLastletters
What would be an easier way to set $strModuleToTestUppercase? I tried
strModuleToTestUppercase="${strModuleToTest[#]^}"
or
strModuleToTestUppercasesed="sed 's/\<./\u&/g' $strModuleToTest"
but without success.
If:
s=somemodule
with bash v4+
echo ${s^}
This should work with a bit older bash versions (from Glenn):
echo $(tr a-z A-Z <<< ${s:0:1})${s:1}")
with zsh
echo ${(C)s}
with ash and coreutils
echo $(echo $s | cut -c1 | tr a-z A-Z)$(echo $s | cut -c2-)
with GNU sed
echo $s | sed 's/./\U&/'
with BSD sed
echo $s | sed '
h;
y/quvwxzdermatoglyphicsbfjkn/QUVWXZDERMATOGLYPHICSBFJKN/;
G;
s/\(.\)[^\n]*\n.\(.*\)/\1\2/;
'
with awk
echo $s | awk '{ print toupper(substr($0, 1, 1)) substr($0, 2) }'
with perl
echo $s | perl -nE 'say ucfirst'
with python
echo $s | python -c 'import sys; print sys.stdin.readline().rstrip().capitalize()'
with ruby
echo $s | ruby -e 'puts ARGF.read.capitalize'
Output in all cases
Somemodule
You could use
strModuleToTestUpper=`sed 's/\(.\)/\U\1/' <<< "$strModuleToTest"`
Explanation
\(.\) matches a single character
\U\1 replaces that character with an uppercase version
no /g means only the first match is processed.
Is perl ok?
$ x=foobar
$ x=$(echo "$x" | perl -pe 's/^(.)/uc($1)/e')
$ echo $x
Foobar
Related
I have a variable having following format
bundle="chn-pro-X.Y-Z.el8.x86_64"
X,Y,Z are numbers having any number of digits
Ex:
1.0-2 # X=1 Y=0 Z=2
12.45-9874 # X=12 Y=45 Z=9874
How can I grab X.Y and store it in another variable?
EDIT:
I wasn't right with my wording, but
I want to store X.Y into new variable not individual X & Y's
I'm looking to finally have a variable version which has X.Y grabbed from bundle:
version="X.Y"
I would use awk:
bundle="chn-pro-12.45-9874.el8.x86_64"
echo "$bundle" | awk -F "[.-]" '{print $3,$4,$5}'
12 45 9874
Now if you want to assign to x, y, z use read and process substitution:
read -r x y z < <(echo "$bundle" | awk -F "[.-]" '{print $3,$4,$5}')
echo "x=$x, y=$y, z=$z"
x=12, y=45, z=9874
If you just want the value of X.Y as a single value this is still great use for awk:
bundle="chn-pro-12.45-9874.el8.x86_64"
echo "$bundle" | awk -F "[-]" '{print $3}'
12.45
And if you then want to put that into a variable:
x_y=$(echo "$bundle" | awk -F "[-]" '{print $3}')
echo "x_y=$x_y"
x_y=12.45
Or you can use cut in this case to get the third field:
echo "$bundle" | cut -d- -f3
12.45
Like that:
$ bundle="chn-pro-1.0-2.el8.x86_64"
$ X="$(echo "$bundle" | cut -d . -f1 | cut -d- -f3)"
$ Y="$(echo "$bundle" | cut -d . -f2 | cut -d- -f1)"
$ Z="$(echo "$bundle" | cut -d . -f2 | cut -d- -f2)"
$ echo "$X"
1
$ echo "$Y"
0
$ echo "$Z"
2
You can merge X and Y into a single variable:
$ XY="$X.$Y"
$ echo $XY
1.0
Use regex to separate numbers:
numbers=$(echo $bundle | grep -Eo '([0-9]+\.[0-9]+\-[0-9]+)' | sed 's/\./\t/g;s/\-/\t/g')
Then assign them to variables with using awk or tr or cut, whatever you want:
X=$(echo $numbers| awk '{print $1}')
Y=$(echo $numbers| awk '{print $2}')
Z=$(echo $numbers| awk '{print $3}')
EDIT
For storing x.y into single version variable you can simply ignore pervios commands:
version=$(echo $bundle | grep -Eo '([0-9]+\.[0-9]+\-[0-9]+)' | grep -Eo '([0-9]+\.[0-9]+)')
Given this input:
$ bundle="chn-pro-12.45-9874.el8.x86_64"
using GNU or BSD sed for -E:
$ foo=$(echo "$bundle" | sed -E 's/.*-([0-9]+\.[0-9]+)-[0-9].*/\1/')
$ echo "$foo"
12.45
or with any sed:
$ foo=$(echo "$bundle" | sed 's/.*-\([0-9][0-9]*\.[0-9][0-9]*\)-[0-9].*/\1/')
$ echo "$foo"
12.45
Assumptions:
the input string will always contain (at least) 3 hyphens
the desired version string will always reside between the 2nd and 3rd hyphens of the input string
we need to maintain the input string (ie, don't clobber/overwrite the variable containing the input string)
We can eliminate the subprocess calls (necessary for echo/sed/grep/awk/sed) by using some parameter expansions:
$ bundle="chn-pro-X.Y-Z.el8.x86_64"
$ temp="${bundle#*-}" # strip off 1st hyphen delimited string
$ echo "${temp}"
pro-X.Y-Z.el8.x86_64
$ temp="${temp#*-}" # strip off 2nd hyphen delimited string
$ echo "${temp}"
X.Y-Z.el8.x86_64
$ version="${temp%%-*}" # save 3rd hyphen delimited string (aka our version)
$ echo "${version}"
X.Y
NOTE: We can eliminate the temp variable by replacing all occurrences of temp with version with the understanding version does not contain what we want until after the 3rd parameter expansion has occurred, eg:
$ bundle="chn-pro-X.Y-Z.el8.x86_64"
$ version="${bundle#*-}"
$ version="${version#*-}"
$ version="${version%%-*}"
$ echo "${version}"
X.Y
I have paths.txt like:
pathO1/:pathD1/
pathO2/:pathD2/
...
pathON/:pathDN/
How can I 'sed' insert ' * ' after each pathOX/ ?
The script is:
while read line
do
cp $(echo $line | tr ':' ' ')
done < "paths.txt"
substituted by:
while read line
do
cp $(echo $line | sed 's/:/* /1')
done < "paths.txt"
This looks to be a similar question to which you asked earlier: Shell Script: Read line in file
Just apply the trick of removing additional '*' before appliying tr like:
cp $(echo $line | sed 's/\*//1' | tr ':' '* ')
while read line
do
path=`echo "$line" | sed 's/:/ /g'`
cmd="cp $path"
echo $cmd
eval $cmd
done < "./paths.txt"
quick and dirty awk one-liner without loop to do the job:
awk -F: '$1="cp "$1' paths.txt
this will output:
cp /home/Documents/shellscripts/Origen/* /home/Documents/shellscripts/Destino/
cp /home/Documents/shellscripts/Origen2/* /home/Documents/shellscripts/Destino2/
...
if you want the cmds to get executed:
awk -F: '$1="cp "$1' paths.txt|sh
I said it quick & dirty, because:
the format must be path1:path2
your path cannot contain special letters (like space) or :
Using pure shell
while IFS=: read -r p1 p2
do
cp $p1 "$p2"
done < file
I want to uppercase just the first character in my string with bash.
foo="bar";
//uppercase first character
echo $foo;
should print "Bar";
One way with bash (version 4+):
foo=bar
echo "${foo^}"
prints:
Bar
foo="$(tr '[:lower:]' '[:upper:]' <<< ${foo:0:1})${foo:1}"
One way with sed:
echo "$(echo "$foo" | sed 's/.*/\u&/')"
Prints:
Bar
$ foo="bar";
$ foo=`echo ${foo:0:1} | tr '[a-z]' '[A-Z]'`${foo:1}
$ echo $foo
Bar
To capitalize first word only:
foo='one two three'
foo="${foo^}"
echo $foo
One two three
To capitalize every word in the variable:
foo="one two three"
foo=( $foo ) # without quotes
foo="${foo[#]^}"
echo $foo
One Two Three
(works in bash 4+)
Using awk only
foo="uNcapItalizedstrIng"
echo $foo | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'
Here is the "native" text tools way:
#!/bin/bash
string="abcd"
first=`echo $string|cut -c1|tr [a-z] [A-Z]`
second=`echo $string|cut -c2-`
echo $first$second
just for fun here you are :
foo="bar";
echo $foo | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1'
# or
echo ${foo^}
# or
echo $foo | head -c 1 | tr [a-z] [A-Z]; echo $foo | tail -c +2
# or
echo ${foo:1} | sed -e 's/^./\B&/'
It can be done in pure bash with bash-3.2 as well:
# First, get the first character.
fl=${foo:0:1}
# Safety check: it must be a letter :).
if [[ ${fl} == [a-z] ]]; then
# Now, obtain its octal value using printf (builtin).
ord=$(printf '%o' "'${fl}")
# Fun fact: [a-z] maps onto 0141..0172. [A-Z] is 0101..0132.
# We can use decimal '- 40' to get the expected result!
ord=$(( ord - 40 ))
# Finally, map the new value back to a character.
fl=$(printf '%b' '\'${ord})
fi
echo "${fl}${foo:1}"
This works too...
FooBar=baz
echo ${FooBar^^${FooBar:0:1}}
=> Baz
FooBar=baz
echo ${FooBar^^${FooBar:1:1}}
=> bAz
FooBar=baz
echo ${FooBar^^${FooBar:2:2}}
=> baZ
And so on.
Sources:
Bash Manual: Shell Parameter Expansion
Full Bash Guide: Parameters
Bash Hacker's Wiki Parameter Expansion
Inroductions/Tutorials:
Cyberciti.biz: 8. Convert to upper to lower case or vice versa
Opensource.com: An introduction to parameter expansion in Bash
This one worked for me:
Searching for all *php file in the current directory , and replace the first character of each filename to capital letter:
e.g: test.php => Test.php
for f in *php ; do mv "$f" "$(\sed 's/.*/\u&/' <<< "$f")" ; done
Alternative and clean solution for both Linux and OSX, it can also be used with bash variables
python -c "print(\"abc\".capitalize())"
returns Abc
This is POSIX sh-compatible as far as I know.
upper_first.sh:
#!/bin/sh
printf "$1" | cut -c1 -z | tr -d '\0' | tr [:lower:] [:upper:]
printf "$1" | cut -c2-
cut -c1 -z ends the first string with \0 instead of \n. It gets removed with tr -d '\0'. It also works to omit the -z and use tr -d '\n' instead, but this breaks if the first character of the string is a newline.
Usage:
$ upper_first.sh foo
Foo
$
In a function:
#!/bin/sh
function upper_first ()
{
printf "$1" | cut -c1 -z | tr -d '\0' | tr [:lower:] [:upper:]
printf "$1" | cut -c2-
}
old="foo"
new="$(upper_first "$old")"
echo "$new"
Posix compliant and with less sub-processes:
v="foo[Bar]"
printf "%s" "${v%"${v#?}"}" | tr '[:lower:]' '[:upper:]' && printf "%s" "${v#?}"
==> Foo[Bar]
first-letter-to-lower () {
str=""
space=" "
for i in $#
do
if [ -z $(echo $i | grep "the\|of\|with" ) ]
then
str=$str"$(echo ${i:0:1} | tr '[A-Z]' '[a-z]')${i:1}$space"
else
str=$str${i}$space
fi
done
echo $str
}
first-letter-to-upper-xc () {
v-first-letter-to-upper | xclip -selection clipboard
}
first-letter-to-upper () {
str=""
space=" "
for i in $#
do
if [ -z $(echo $i | grep "the\|of\|with" ) ]
then
str=$str"$(echo ${i:0:1} | tr '[a-z]' '[A-Z]')${i:1}$space"
else
str=$str${i}$space
fi
done
echo $str
}
first-letter-to-lower-xc(){
v-first-letter-to-lower | xclip -selection clipboard
}
Not exactly what asked but quite helpful
declare -u foo #When the variable is assigned a value, all lower-case characters are converted to upper-case.
foo=bar
echo $foo
BAR
And the opposite
declare -l foo #When the variable is assigned a value, all upper-case characters are converted to lower-case.
foo=BAR
echo $foo
bar
What if the first character is not a letter (but a tab, a space, and a escaped double quote)? We'd better test it until we find a letter! So:
S=' \"ó foo bar\"'
N=0
until [[ ${S:$N:1} =~ [[:alpha:]] ]]; do N=$[$N+1]; done
#F=`echo ${S:$N:1} | tr [:lower:] [:upper:]`
#F=`echo ${S:$N:1} | sed -E -e 's/./\u&/'` #other option
F=`echo ${S:$N:1}
F=`echo ${F} #pure Bash solution to "upper"
echo "$F"${S:(($N+1))} #without garbage
echo '='${S:0:(($N))}"$F"${S:(($N+1))}'=' #garbage preserved
Foo bar
= \"Foo bar=
In string "12345", out string "54321". Preferably without third party tools and regex.
I know you said "without third-party tools", but sometimes a tool is just too obviously the right one, plus it's installed on most Linux systems by default:
[madhatta#risby tmp]$ echo 12345 | rev
54321
See rev's man page for more.
Simple:
var="12345"
copy=${var}
len=${#copy}
for((i=$len-1;i>=0;i--)); do rev="$rev${copy:$i:1}"; done
echo "var: $var, rev: $rev"
Output:
$ bash rev
var: 12345, rev: 54321
Presume that a variable 'var' has the value '123'
var="123"
Reverse the string and store in a new variable 'rav':
rav=$(echo $var | rev)
You'll see the 'rav' has the value of '321' using echo.
echo $rav
rev | tail -r (BSD) or rev | tac (GNU) also reverse lines:
$ rev <<< $'12\n34' | tail -r
43
21
$ rev <<< $'12\n34' | gtac
43
21
If LC_CTYPE is C, rev reverses the bytes of multibyte characters:
$ LC_CTYPE=C rev <<< あの
��め�
$ export LC_ALL=C; LC_ALL=en_US.UTF-8 rev <<< あの
のあ
A bash solution improving over #osdyng answer (my edit was not accepted):
var="12345" rev=""
for(( i=0 ; i<${#var} ; i++ )); do rev="${var:i:1}$rev"; done
echo "var: $var, rev: $rev"
Or an even simpler (bash) loop:
var=$1 len="${#var}" i=0 rev=""
while (( i<len )); do rev="${var:i++:1}$rev"; done
echo "var: $var, rev: $rev"
A POSIX solution:
var="12345" rev="" i=1
while [ "$i" -le "${#var}" ]
do rev="$(echo "$var" | awk -v i="$i" '{print(substr($0,i,1))}')$rev"
: $(( i+=1 ))
done
echo "var: $var, rev: $rev"
Note: This works on multi byte strings. Cut solutions will work only in ASCII (1 byte) strings.
Some simple methods of reversing a string
echo '!!!esreveR si sihT' | grep -o . | tac | tr -d '\n' ; echo
echo '!!!esreveR si sihT' | fold -w 1 | tac | tr -d '\n' ; echo
Convert to hex values then reverse
echo '!!!esreveR si sihT' | xxd -p | grep -o .. | tac | xxd -r -p ; echo
echo '!!!esreveR si sihT' | xxd -p | fold -w 2 | tac | xxd -r -p ; echo
This reverses the string "in place":
a=12345
len=${#a}
for ((i=1;i<len;i++)); do a=$a${a: -i*2:1}; done; a=${a:len-1}
echo $a
or the third line could be:
for ((i=0;i<len;i++)); do a=${a:i*2:1}$a; done; a=${a:0:len}
or
for ((i=1;i<len;i++)); do a=${a:0:len-i-1}${a: -i:i+1}${a:len-i-1:1}; done
For those without rev (recommended), there is the following simple awk solution that splits fields on the null string (every character is a separate field) and prints in reverse:
awk -F '' '{ for(i=NF; i; i--) printf("%c", $i); print "" }'
The above awk code is POSIX compliant. As a compliant awk implementation is guaranteed to be on every POSIX compliant OS, the solution should thus not be thought of as "3rd-party." This code will likely be more concise and understandable than a pure POSIX sh (or bash) solution.
(; I do not know if you consider the null string to -F a regex... ;)
If var=12345:
bash for((i=0;i<${#var};i++)); do rev="$rev${var:~i:1}"; done
sh c=$var; while [ "$c" ]; do rev=$rev${c#"${c%?}"}; c=${c%?}; done
echo "var: $var, rev: $rev"
Run it:
$ rev
var: 12345, rev: 54321
This can of course be shortened, but it should be simple to understand: the final print adds the newline.
echo 12345 | awk '{for (i = length($0); i > 0; i--) {printf("%s", substr($0, i, 1));} print "";}'
Nobody appears to have posted a sed solution, so here's one that works in non-GNU sed (so I wouldn't consider it "3rd party"). It does capture single characters using the regex ., but that's the only regex.
In two stages:
$ echo 123456 | sed $'s/./&\\\n/g' | sed -ne $'x;H;${x;s/\\n//g;p;}'
654321
This uses bash format substitution to include newlines in the scripts (since the question is tagged bash). It works by first separating the input string into one line per character, and then by inserting each character into the beginning of the hold buffer.
x swaps the hold space and the pattern space, and
H H appends the (current) pattern space to the hold space.
So for every character, we place that character into the hold space, then append the old hold space to it, thus reversing the input. The final command removes the newlines in order to reconstruct the original string.
This should work for any single string, but it will concatenate multi-line input into a single output string.
Here is another simpler awk solution:
awk 'BEGIN{FS=""} {for (i=NF; i>0; i--) s=s $i; print s}' <<< '123456'
654321
Try Perl:
echo 12345 | perl -nle 'print scalar reverse $_'
Source: Perl one-liners
read word
reve=`echo "$word" | awk '{for(i=length($0); i>0;i--) printf (substr($0,i,1));}'`
echo "$reve"
Suppose I have the string 1:2:3:4:5 and I want to get its last field (5 in this case). How do I do that using Bash? I tried cut, but I don't know how to specify the last field with -f.
You can use string operators:
$ foo=1:2:3:4:5
$ echo ${foo##*:}
5
This trims everything from the front until a ':', greedily.
${foo <-- from variable foo
## <-- greedy front trim
* <-- matches anything
: <-- until the last ':'
}
Another way is to reverse before and after cut:
$ echo ab:cd:ef | rev | cut -d: -f1 | rev
ef
This makes it very easy to get the last but one field, or any range of fields numbered from the end.
It's difficult to get the last field using cut, but here are some solutions in awk and perl
echo 1:2:3:4:5 | awk -F: '{print $NF}'
echo 1:2:3:4:5 | perl -F: -wane 'print $F[-1]'
Assuming fairly simple usage (no escaping of the delimiter, for example), you can use grep:
$ echo "1:2:3:4:5" | grep -oE "[^:]+$"
5
Breakdown - find all the characters not the delimiter ([^:]) at the end of the line ($). -o only prints the matching part.
You could try something like this if you want to use cut:
echo "1:2:3:4:5" | cut -d ":" -f5
You can also use grep try like this :
echo " 1:2:3:4:5" | grep -o '[^:]*$'
One way:
var1="1:2:3:4:5"
var2=${var1##*:}
Another, using an array:
var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
var2=${var2[#]: -1}
Yet another with an array:
var1="1:2:3:4:5"
saveIFS=$IFS
IFS=":"
var2=($var1)
IFS=$saveIFS
count=${#var2[#]}
var2=${var2[$count-1]}
Using Bash (version >= 3.2) regular expressions:
var1="1:2:3:4:5"
[[ $var1 =~ :([^:]*)$ ]]
var2=${BASH_REMATCH[1]}
$ echo "a b c d e" | tr ' ' '\n' | tail -1
e
Simply translate the delimiter into a newline and choose the last entry with tail -1.
Using sed:
$ echo '1:2:3:4:5' | sed 's/.*://' # => 5
$ echo '' | sed 's/.*://' # => (empty)
$ echo ':' | sed 's/.*://' # => (empty)
$ echo ':b' | sed 's/.*://' # => b
$ echo '::c' | sed 's/.*://' # => c
$ echo 'a' | sed 's/.*://' # => a
$ echo 'a:' | sed 's/.*://' # => (empty)
$ echo 'a:b' | sed 's/.*://' # => b
$ echo 'a::c' | sed 's/.*://' # => c
There are many good answers here, but still I want to share this one using basename :
basename $(echo "a:b:c:d:e" | tr ':' '/')
However it will fail if there are already some '/' in your string.
If slash / is your delimiter then you just have to (and should) use basename.
It's not the best answer but it just shows how you can be creative using bash commands.
If your last field is a single character, you could do this:
a="1:2:3:4:5"
echo ${a: -1}
echo ${a:(-1)}
Check string manipulation in bash.
Using Bash.
$ var1="1:2:3:4:0"
$ IFS=":"
$ set -- $var1
$ eval echo \$${#}
0
echo "a:b:c:d:e"|xargs -d : -n1|tail -1
First use xargs split it using ":",-n1 means every line only have one part.Then,pring the last part.
Regex matching in sed is greedy (always goes to the last occurrence), which you can use to your advantage here:
$ foo=1:2:3:4:5
$ echo ${foo} | sed "s/.*://"
5
A solution using the read builtin:
IFS=':' read -a fields <<< "1:2:3:4:5"
echo "${fields[4]}"
Or, to make it more generic:
echo "${fields[-1]}" # prints the last item
for x in `echo $str | tr ";" "\n"`; do echo $x; done
improving from #mateusz-piotrowski and #user3133260 answer,
echo "a:b:c:d::e:: ::" | tr ':' ' ' | xargs | tr ' ' '\n' | tail -1
first, tr ':' ' ' -> replace ':' with whitespace
then, trim with xargs
after that, tr ' ' '\n' -> replace remained whitespace to newline
lastly, tail -1 -> get the last string
For those that comfortable with Python, https://github.com/Russell91/pythonpy is a nice choice to solve this problem.
$ echo "a:b:c:d:e" | py -x 'x.split(":")[-1]'
From the pythonpy help: -x treat each row of stdin as x.
With that tool, it is easy to write python code that gets applied to the input.
Edit (Dec 2020):
Pythonpy is no longer online.
Here is an alternative:
$ echo "a:b:c:d:e" | python -c 'import sys; sys.stdout.write(sys.stdin.read().split(":")[-1])'
it contains more boilerplate code (i.e. sys.stdout.read/write) but requires only std libraries from python.