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.

3

5 Answers 5

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
9
  • 3
    Great! It works like a charm! I will select the read since i'm using bash. Thank you @manatwork!
    – gakhov
    Oct 30, 2012 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. Oct 30, 2012 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, 2012 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. Oct 30, 2012 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, 2014 at 8:14
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
2
  • 1
    Amazing thanks! Best answer. Although you need to do text1=${text%";$text2"} to remove the trailing ; in $1text
    – pez
    Mar 17, 2020 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. Jun 1, 2023 at 8: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

3
  • 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 Oct 31, 2012 at 12:43
  • 1
    How is this answer different from @StephaneChazelas?
    – Bernhard
    Oct 31, 2012 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 9, 2020 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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .