3

Given the string "foo-bar-1.4.5" or "for-bar-baz-1.8.3" how can I break these strings into 2 parts: first part everything before the last "-" ("foo-bar", "foo-bar-baz") and the last part ("1.4.5" and "1.8.3") respectively?

I can imagine a few strategies, like splitting on "-" then adding everything but the last part back together. Or maybe a regex with substitution. Bash is the biggest thing in my way though. I'm not sure what the syntax would look like and I've tried a few things with sed and awk but my lack of comfort with bash arrays is hanging me up. I figure one of you bash experts can bang this out in short order and teach me via an example pretty fast.

EDIT: I won't know the number of "-" before the command is run. The method should handle n >= 1 of "-".

Dane O'Connor
  • 75,180
  • 37
  • 119
  • 173
  • What is your input source? Do you have a file full of these, or a stream? You're not against sed & awk? What about Perl? – Michael Berkowski May 02 '14 at 15:41
  • (in other words, you're comfortable with more than raw bash for this?) – Michael Berkowski May 02 '14 at 15:41
  • @MichaelBerkowsk I only have packages installed with fresh debian:jessie. So perl is out. But `sed` and `awk` are ok. – Dane O'Connor May 02 '14 at 15:43
  • possible duplicate of [How to get second last field from a cut command](http://stackoverflow.com/questions/17644000/how-to-get-second-last-field-from-a-cut-command) – njzk2 May 02 '14 at 15:43
  • [Bash can do lots of stuff all by itself.](http://mywiki.wooledge.org/BashFAQ/100) – Mat May 02 '14 at 15:44
  • 1
    (in short `echo "foo-bar-bax-1.8.3" | awk -F'-' '{print $(NF-1)}'` or something) – njzk2 May 02 '14 at 15:45
  • @njzk2 I disagree with the duplication suggestions. Even if the tools used to solve the problem are the same, the goal is different. Your method looks interesting though! I didn't know about NF. – Dane O'Connor May 02 '14 at 15:51
  • You could use `cut` too! – devnull May 02 '14 at 15:53
  • @thedeeno: the linked question is about getting the `second last field`. getting the last field is rather close, I would say. – njzk2 May 02 '14 at 16:55

3 Answers3

7

You can use parameter expansion for this:

${var#pattern} removes the shortest matching pattern from the beginning of var. Using ## removes the longest matching pattern. % and %% work similarly, but remove from the end of var.

#!/bin/bash
first="foo-bar-1.4.5"
second="foo-bar-baz-1.8.3"

echo ${first%-*}
echo ${first##*-}

echo ${second%-*}
echo ${second##*-}

Output:

foo-bar
1.4.5
foo-bar-baz
1.8.3
Josh Jolly
  • 11,258
  • 2
  • 39
  • 55
2

To use a regular expression in bash:

s="foo-bar-1.4.5"
[[ $s =~ (.*)-(.*) ]]
name=${BASH_REMATCH[1]}
version=${BASH_REMATCH[2]}

bash only uses greedy matching, so the first .* will match as much as possible, leaving only the last - to match the literal - in the regular expression.

chepner
  • 497,756
  • 71
  • 530
  • 681
1

Using bash built-in features is the right way to go. However, for reference, here is an option using awk:

$ cat file
foo-bar-1.4.5
for-bar-baz-1.8.3

$ awk 'BEGIN{FS=OFS="-"}{last = $NF; NF--; printf "First = %s\nLast = %s\n", $0, last}' file
First = foo-bar
Last = 1.4.5
First = for-bar-baz
Last = 1.8.3
jaypal singh
  • 74,723
  • 23
  • 102
  • 147