95

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
  • 67,283
  • 35
  • 116
  • 255
gakhov
  • 1,105

5 Answers5

120

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
  • 31,277
  • 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? – Wildcard Nov 17 '16 at 18:56
27

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.

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

  • 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
  • 6
    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.