'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 like my.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 '[^ ]*$'
  1. the -o says to only retain the matching portion of the string
  2. the [^ ] part says "don't match spaces"; ie: "not the space char"
  3. 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.123 in 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:

  1. Option 1 (will "break in mysterious ways", as @tripleee put it in a comment here, if the string stored in the 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)
    
  2. Option 2 [RECOMMENDED!]. Use the 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:

  1. https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arithmetic-Expansion
  2. https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Arithmetic

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