9
votes

Here's my situation. Currently, I have a script that accepts two arguments: book name and chapter name. For example:

$ myscript book1 chap1

Now, for reasons that would take a long time to explain, I would prefer my script to be able to take a single argument of the following format: {book name}.{chapter name}. For example:

$ myscript book1.chap1

The difficulty for me is that I do not know how to take a string $1=abc.xyz and turn it into two separate variables, $var1=abc and $var2=xyz. How can I do this?

6

6 Answers

17
votes

If it's just two tags you can use a bash expression

arg=$1
beforedot=${arg%.*}
afterdot=${arg#*.}

It's faster than cut because it's a shell builtin. Note that this puts everything before the first last dot into beforedot and everything after into afterdot.

EDIT:

There's also a substitution/reinterpretation construct if you want to split by an arbitrary number of tokens:

string=a.b.c.d.e
tokens=(${string//\./ })

You're replacing dots by spaces and then that gets interpreted as an array declaration+definition because of the parentheses around it.

However I've found this to be less portable to bash' siblings and offspring. For example, it doesn't work in my favourite shell, zsh.

Arrays need to be dereferenced with braces and are indexed from 0:

echo "Third token: ${tokens[2]}"

You can loop through them as well by dereferencing the whole array with [@]:

for i in ${tokens[@]}
do
    # do stuff
done
2
votes

For completeness and since you asked about a regex method:

pattern='^([^.]*)\.(.*)'
[[ $1 =~ $pattern ]]
book=${BASH_REMATCH[1]}
chapter=${BASH_REMATCH[2]}

The capture groups are elements in the BASH_REMATCH array. Element 0 contains the whole match.

This regex will capture up to the first dot in the first element. Anything after the first dot including susbsequent dots will be in the second element. The regex can be easily modified to break on the last dot if needed.

1
votes

If $arg contains book.chap

read BOOK CHAP<<<$(IFS="."; echo $arg)

will set the variables BOOK and CHAP accordingly. This uses the bash internal field separator (IFS) which controls how bash understands word boundaries. If (say) you have multiple separators in your original $arg then just specify further variables to contain the results.

From here:

$IFS defaults to whitespace (space, tab, and newline), but may be changed, for example, to parse a comma-separated data file

0
votes

You can use parentheses to capture the two parts; afterwards, you can use backreferences to grab them again. The syntax differs between languages; check http://www.regular-expressions.info/brackets.html for a lesson on backreferences in general.

0
votes
#!/bin/bash

book=${1%.*}
chapter=${1#*.}

printf 'book: %s\nchapter: %s\n' "$book" "$chapter"
0
votes

Pattern Subsitution with Shell Parameter Expansion

There are a lot of ways to accomplish what you're trying to do. One of the ways not covered in other answers is pattern substitution.

If you know that the value will always split correctly on a period, you can apply pattern substitution to the value so that it will be easy to tokenize with IFS. For example:

set -- foo.bar
myvar="${1/./ }"
echo $myvar

This will yield foo bar.