98

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
  • After the string is split what do you want to do with the "id"? Do you want to assign it to a variable, print it out, etc? Commented Oct 30, 2012 at 13:06
  • I gonna have 2 variables: id and string Commented Oct 30, 2012 at 13:07
  • Also see Why is using a shell loop to process text considered bad practice? Commented Nov 17, 2016 at 18:55

5 Answers 5

124

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! Commented 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. Commented 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.) Commented 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. Commented 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 Commented Jun 27, 2014 at 8:14
28

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.

10

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 Commented 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. Commented Jun 1, 2023 at 8:22
9

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 Commented Oct 31, 2012 at 12:43
  • 1
    How is this answer different from @StephaneChazelas? Commented Oct 31, 2012 at 12:50
  • 7
    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). Commented 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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.