94

I have a string in the next format

id;some text here with possible ; inside

and want to split it to 2 strings by first occurrence of the ;. So, it should be: id and some text here with possible ; inside

I know how to split the string (for instance, with cut -d ';' -f1), but it will split to more parts since I have ; inside the left part.

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
gakhov
  • 1,095
  • 2
  • 8
  • 9

5 Answers5

117

cut sounds like a suitable tool for this:

bash-4.2$ s='id;some text here with possible ; inside'

bash-4.2$ id="$( cut -d ';' -f 1 <<< "$s" )"; echo "$id"
id

bash-4.2$ string="$( cut -d ';' -f 2- <<< "$s" )"; echo "$string"
some text here with possible ; inside

But read is even more suitable:

bash-4.2$ IFS=';' read -r id string <<< "$s"

bash-4.2$ echo "$id"
id

bash-4.2$ echo "$string"
some text here with possible ; inside
manatwork
  • 30,549
  • 7
  • 101
  • 91
  • 3
    Great! It works like a charm! I will select the `read` since i'm using `bash`. Thank you @manatwork! – gakhov Oct 30 '12 at 13:45
  • The `cut` approach will only work when "$s" doesn't contain newline characters. read is in any Bourne-like shell. <<< is in rc, zsh and recent versions of bash and ksh93 and is the one that is not standard. – Stéphane Chazelas Oct 30 '12 at 14:24
  • Oops, you are right @StephaneChazelas. My mind was at `-a` for some reason when mentioning `bash`'s `read`. (Evidently of no use here.) – manatwork Oct 30 '12 at 14:34
  • I forgot to mention that the read approach doesn't work if $s contains newline characters either. I've added my own answer. – Stéphane Chazelas Oct 30 '12 at 14:37
  • 2
    I would like to emphasize the trailing dash in `-f 2-` in the `string="$( cut -d ';' -f 2- <<< "$s" )"; echo "$string"` command. This is what ignores the rest of the delimiters in the string for the printout. Not obvious when looking at the man page of `cut` – Steen Jun 27 '14 at 08:14
  • Even better, for the first example, is to set `string` equal to `"${s:$((${#id} + 1))}"`. This slices off the first string from the original string, plus a character for the `;`. – skeggse Sep 25 '14 at 19:59
  • @distilledchaos, yes, that is also a way. I will keep the first example with `cut` as that is the question owner had problem with. Regarding your suggestion, note that parameter expansion's arguments are subject to arithmetic evaluation, so the explicit `$((…))` is not necessary: `string="${s:${#id} + 1}"`. – manatwork Sep 26 '14 at 08:11
  • great! This works perfect for me. Thumbs up. – Zain Raza Nov 12 '15 at 15:53
  • `read` is fine, as long as you're not inside a `while` loop. Please, let's all of use learn [Why is using a shell loop to process text considered bad practice?](http://unix.stackexchange.com/q/169716/135943) – Wildcard Nov 17 '16 at 18:56
25

With any standard sh (including bash):

sep=';'
case $s in
  (*"$sep"*)
    before=${s%%"$sep"*}
    after=${s#*"$sep"}
    ;;
  (*)
    before=$s
    after=
    ;;
esac

read based solutions would work for single character (and with some shells, single-byte) values of $sep other than space, tab or newline and only if $s doesn't contain newline characters.

cut based solutions would only work if $s doesn't contain newline characters.

sed solutions could be devised that handle all the corner cases with any value of $sep, but it's not worth going that far when there's builtin support in the shell for that.

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
9

Solution in standard bash:

    text='id;some text here with possible ; inside'
    text2=${text#*;}
    text1=${text%"$text2"}

    echo $text1
    #=> id;
    echo $text2
    #=> some text here with possible ; insideDD
ethaning
  • 191
  • 1
  • 3
  • 1
    Amazing thanks! Best answer. Although you need to do `text1=${text%";$text2"}` to remove the trailing `;` in `$1text` – pez Mar 17 '20 at 19:38
  • @pez, but with that, for values of `$text` not containing `;`, you'll end up with both `$text1` and `$text2` containing `$text`. You'd need `text1=${text%%;*}; text2=${text#"$text1"}; text2=${text2#;}`, or handle it as a special case like in [my answer](/a/53323). – Stéphane Chazelas Jun 01 '23 at 08:22
8

As you have mentioned that you want to assign the values to id and string

first assign your pattern to a variable(say str)

    str='id;some text here with possible ; inside'
    id=${str%%;} 
    string=${str#;}

Now you have your values in respective variables

user1678213
  • 594
  • 3
  • 9
  • if you are getting your pattern from a command then use set -- `some_command` ,then your pattern will get stored in $1 and use the above code with 1 instead of str – user1678213 Oct 31 '12 at 12:43
  • 1
    How is this answer different from @StephaneChazelas? – Bernhard Oct 31 '12 at 12:50
  • 5
    Should be `${str%%;*}` for id and `${str#*;}` for `str` (with the asterisks). This is an old answer though so perhaps this worked on older versions of bash, but these changes were required for me on bash 4.2+ (and possibly earlier, I did not test). – fquinner Dec 09 '20 at 12:48
5

In addition to the other solutions, you could try something regex based:

a="$(sed 's/;.*//' <<< "$s")"
b="$(sed 's/^[^;]*;//' <<< "$s")"

or depending on what you are trying to do exactly, you could use

sed -r 's/^([^;]*);(.*)/\1 ADD THIS TEXT BETWEEN YOUR STRINGS \2/'

where \1 and \2 contain the two substrings you were wanting.

tojrobinson
  • 606
  • 4
  • 7