I have this variable:
A="Some variable has value abc.123"
I need to extract this value i.e abc.123
. Is this possible in bash?
I have this variable:
A="Some variable has value abc.123"
I need to extract this value i.e abc.123
. Is this possible in bash?
Simplest is
echo "$A" | awk '{print $NF}'
Edit: explanation of how this works...
awk
breaks the input into different fields, using whitespace as the separator by default. Hardcoding 5
in place of NF
prints out the 5th field in the input:
echo "$A" | awk '{print $5}'
NF
is a built-in awk
variable that gives the total number of fields in the current record. The following returns the number 5 because there are 5 fields in the string "Some variable has value abc.123"
:
echo "$A" | awk '{print NF}'
Combining $
with NF
outputs the last field in the string, no matter how many fields your string contains.
Yes; this:
A="Some variable has value abc.123"
echo "${A##* }"
will print this:
abc.123
(The ${parameter##word}
notation is explained in §3.5.3 "Shell Parameter Expansion" of the Bash Reference Manual.)
Some examples using parameter expansion
A="Some variable has value abc.123"
echo "${A##* }"
abc.123
Longest match on " " space
echo "${A% *}"
Some variable has value
Longest match on . dot
echo "${A%.*}"
Some variable has value abc
Shortest match on " " space
echo "${A%% *}"
some
Read more Shell-Parameter-Expansion
The documentation is a bit painful to read, so I've summarised it in a simpler way.
Note that the '*
' needs to swap places with the '
' depending on whether you use #
or %
. (The *
is just a wildcard, so you may need to take off your "regex hat" while reading.)
${A% *}
- remove shortest trailing *
(strip the last word)${A%% *}
- remove longest trailing *
(strip the last words)${A#* }
- remove shortest leading *
(strip the first word)${A##* }
- remove longest leading *
(strip the first words)Of course a "word" here may contain any character that isn't a literal space.
You might commonly use this syntax to trim filenames:
${A##*/}
removes all containing folders, if any, from the start of the path, e.g.
/usr/bin/git
-> git
/usr/bin/
-> (empty string)
${A%/*}
removes the last file/folder/trailing slash, if any, from the end:
/usr/bin/git
-> /usr/bin
/usr/bin/
-> /usr/bin
${A%.*}
removes the last extension, if any (just be wary of things like my.path/noext
):
archive.tar.gz
-> archive.tar
How do you know where the value begins? If it's always the 5th and 6th words, you could use e.g.:
B=$(echo "$A" | cut -d ' ' -f 5-)
This uses the cut
command to slice out part of the line, using a simple space as the word delimiter.
As pointed out by Zedfoxus here. A very clean method that works on all Unix-based systems. Besides, you don't need to know the exact position of the substring.
A="Some variable has value abc.123"
echo "$A" | rev | cut -d ' ' -f 1 | rev
# abc.123
More ways to do this:
(Run each of these commands in your terminal to test this live.)
For all answers below, start by typing this in your terminal:
A="Some variable has value abc.123"
The array example (#3 below) is a really useful pattern, and depending on what you are trying to do, sometimes the best.
awk
, as the main answer showsecho "$A" | awk '{print $NF}'
grep
:echo "$A" | grep -o '[^ ]*$'
-o
says to only retain the matching portion of the string[^ ]
part says "don't match spaces"; ie: "not the space char"*
means: "match 0 or more instances of the preceding match pattern (which is [^ ]
), and the $
means "match the end of the line." So, this matches the last word after the last space through to the end of the line; ie: abc.123
in this case.Convert A
to an array, with elements being separated by the default IFS
(Internal Field Separator) char, which is space:
A
variable contains certain special shell characters, so Option 2 below is recommended instead!):
# Capture space-separated words as separate elements in array A_array
A_array=($A)
read
command, as I explain in my answer here, and as is recommended by the bash shellcheck
static code analyzer tool for shell scripts, in ShellCheck rule SC2206, here.
# Capture space-separated words as separate elements in array A_array, using
# a "herestring".
# See my answer here: https://stackoverflow.com/a/71575442/4561887
IFS=" " read -r -d '' -a A_array <<< "$A"
Then, print only the last elment in the array:
# Print only the last element via bash array right-hand-side indexing syntax
echo "${A_array[-1]}" # last element only
Output:
abc.123
Going further:
What makes this pattern so useful too is that it allows you to easily do the opposite too!: obtain all words except the last one, like this:
array_len="${#A_array[@]}"
array_len_minus_one=$((array_len - 1))
echo "${A_array[@]:0:$array_len_minus_one}"
Output:
Some variable has value
For more on the ${array[@]:start:length}
array slicing syntax above, see my answer here: Unix & Linux: Bash: slice of positional parameters, and for more info. on the bash "Arithmetic Expansion" syntax, see here:
You can use a Bash regex:
A="Some variable has value abc.123"
[[ $A =~ [[:blank:]]([^[:blank:]]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "no match"
Prints:
abc.123
That works with any [:blank:]
delimiter in the current local (Usually [ \t]
). If you want to be more specific:
A="Some variable has value abc.123"
pat='[ ]([^ ]+)$'
[[ $A =~ $pat ]] && echo "${BASH_REMATCH[1]}" || echo "no match"
I was following similar questions looking for the best strategy to split result strings in dash. My primary strings were window id separated by either spaces or linefeeds. I loved some of the cleverer answers but in the end this boring solution seemed more performant which surprised me after my experience of python loops:
A="Some variable has value abc.123"
for e in $A; do :; done; echo $e
Basically loop over $A doing nothing (:) then echo the last element. If the tabs or linefeeds are expressed with \t and \n you need to express them first so:
for e in $(echo -e $A); do :; done; echo $e
If the separator was a different character, like : say, the translation process eroded any advantage the simple loop provided but with space separation it worked well. I'd love to hear if there is something I am missing here.