72

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?

0

10 Answers 10

163

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.

4
  • 4
    this is the best answer for command line, I wonder why it isn't more popular Commented Jun 22, 2015 at 23:34
  • 3
    "print NF" print number of words, $NF is the last word.
    – karsten
    Commented Nov 9, 2019 at 15:38
  • No, using shell built-ins is much better when you can avoid an external process. For completeness, perhaps this should show how to use awk -F "x" to use a different separator than space, but again, that's easily done with shell built-ins, too.
    – tripleee
    Commented Apr 16, 2022 at 19:18
  • Simplest is ${A##* }, as in extract=${A##* } or echo ${A##* } edit: as @mwfeamley's answer says.
    – jthill
    Commented Apr 16, 2022 at 19:31
71

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.)

1
  • btw this doesn't work if the string ends with space. (but I agree that it is not clear if it should)
    – noonex
    Commented May 14 at 8:12
21

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

20

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 with other characters 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)
    /usr/bin -> bin

  • ${A%/*} removes the last file/folder/trailing slash, if any, from the end:
    /usr/bin/git -> /usr/bin
    /usr/bin/ -> /usr/bin
    /usr/bin -> /usr/

  • ${A%.*} removes the last extension, if any (just be wary of things like /my.path/noext):
    archive.tar.gz -> archive.tar
    /my.path/noext -> /my (!)

To avoid the last issue, you use an explicit extension if you know it: %.ext instead of %.*

2
  • Is there a solution to remove last extension but do nothing if no extension, on path like my.path/noext? It does happen on /etc/*.d/ for example
    – sayanel
    Commented Nov 13, 2023 at 10:01
  • 1
    Do you know what the extension would be? If e.g. it's .conf, you could do ${A%.conf}. That will leave the string intact if it doesn't end in .conf.
    – mwfearnley
    Commented Nov 13, 2023 at 10:32
14

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.

1
  • Thank you! I was looking everywhere for a way to get from field x to the end of the string and couldn't figure out the wildcard character for list of fields for -f and then stumbled upon the use of - here. What I get for looking at the manual page for cut under BSD I guess.
    – Diffuser
    Commented May 13, 2019 at 15:56
5

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
4

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
2
  • The array variant will break in mysterious ways if the value contains shell metacharacters. You can't quote it, either, because that prevents the shell from breaking it into multiple array elements. (I added quoting where it was missing to several answers here, including earlier in this answer.)
    – tripleee
    Commented Apr 16, 2022 at 19:21
  • 1
    @tripleee, thanks for fixing the quotes. I addressed your point and fixed my answer by changing A_array=($A) to IFS=" " read -r -d '' -a A_array <<< "$A" as a recommended "Option 2" to split the multi-word string into an array. Commented Apr 16, 2022 at 21:14
0

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"
0
echo "Some variable has value abc.123"| perl -nE'say $1 if /(\S+)$/'
0

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.