'How to extract last part of string 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?
Solution 1:[1]
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.
Solution 2:[2]
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.
Solution 3:[3]
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.)
Solution 4:[4]
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
Solution 5:[5]
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 likemy.path/noext):archive.tar.gz->archive.tar
Solution 6:[6]
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
Solution 7:[7]
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.
1. with awk, as the main answer shows
echo "$A" | awk '{print $NF}'
2. with grep:
echo "$A" | grep -o '[^ ]*$'
- the
-osays to only retain the matching portion of the string - the
[^ ]part says "don't match spaces"; ie: "not the space char" - the
*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.123in this case.
3. via regular bash "indexed" arrays and array indexing
Convert A to an array, with elements being separated by the default IFS (Internal Field Separator) char, which is space:
- Option 1 (will "break in mysterious ways", as @tripleee put it in a comment here, if the string stored in the
Avariable 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) - Option 2 [RECOMMENDED!]. Use the
readcommand, as I explain in my answer here, and as is recommended by the bashshellcheckstatic 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:
Solution 8:[8]
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"
Solution 9:[9]
echo "Some variable has value abc.123"| perl -nE'say $1 if /(\S+)$/'
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | tripleee |
| Solution 2 | tripleee |
| Solution 3 | ruakh |
| Solution 4 | koola |
| Solution 5 | |
| Solution 6 | tripleee |
| Solution 7 | |
| Solution 8 | |
| Solution 9 | mrqiao001 |
