17

Example:

before: text_before_specific_character(specific_character)text_to_be_deleted
after: text_before_specific_character

I know that it can be done with sed. But I'm stuck. Can someone help me out?

3 Answers 3

33

There's no reason to use an external tool such as sed for this; bash can do it internally, using parameter expansion:

If the character you want to trim after is :, for instance:

$ str=foo_bar:baz
$ echo "${str%%:*}"
foo_bar

You can do this in both greedy and non-greedy ways:

$ str=foo_bar:baz:qux
$ echo "${str%:*}"
foo_bar:baz
$ echo "${str%%:*}"
foo_bar

Especially if you're calling this inside a tight loop, starting a new sed process, writing into the process, reading its output, and waiting for it to exit (to reap its PID) can be substantial overhead that doing all your processing internal to bash won't have.


Now -- often, when wanting to do this, what you might really want is to split a variable into fields, which is better done with read.

For instance, let's say that you're reading a line from /etc/passwd:

line=root:x:0:0:root:/root:/bin/bash
IFS=: read -r name password_hashed uid gid fullname homedir shell _ <<<"$line"
echo "$name" # will emit "root"
echo "$shell" # will emit "/bin/bash"

Even if you want to process multiple lines from a file, this too can be done with bash alone and no external processes:

while read -r; do
  echo "${REPLY%%:*}"
done <file

...will emit everything up to the first : from each line of file, without requiring any external tools to be launched.

2
  • this works from right to left? is there anything that works from left to right? what if you dont know how many of these special characters there are? you just want to cut the first one from left to right.
    – Tim Boland
    Commented Jul 10, 2019 at 20:58
  • @TimBoland Just as % deals with suffixes, # deals with prefixes, so both are available. If you want to just read everything into an array, that's what IFS=: read -r -a arrayname is for. Commented Jul 10, 2019 at 21:49
11

What you're looking for is actually really easy:

sed 's/A.*//'

Where A marks the specific character. Note that it is case sensitive, if you want to catch multiple characters use

sed 's/[aAbB].*//'
2

If TEXT contains your text, as in

TEXT=beforexafter

and the specific character happens (for example) to be x, then

echo "${TEXT%x*}"

does what you want.

To Bash, "$TEXT" or "${TEXT}" is the whole beforexafter, but "${TEXT%xafter}" is just before, with the xafter chopped off the end. To chop off the x and anything that might follow it, one writes "${TEXT%x*}".

There is also "${TEXT%%x*}", incidentally. This differs from the other only if there is more than one x. With the %%, Bash chops off all x, whereas with the % it chops off only from the last x. You can remember this by observing loosely that the longer %% chops off more text.

You can do likewise with Sed, of course, if you prefer:

echo "$TEXT" | sed 's/x.*//'
3
  • 2
    Please put quotes around expansions -- echo $TEXT will do string-splitting, so if TEXT contains $'foo\t\tbar\nbaz', it'll come out simply as foo bar baz, whereas echo "$TEXT" doesn't exhibit this flaw. (Perpetuating the all-caps non-environment variable thing is also a bit unfortunate; best practice is to make them lower case to avoid namespace conflicts with environment variables and builtins). Commented Jun 29, 2012 at 16:37
  • @CharlesDuffy: You are right about the quotes: omitting them is a bad habit of mine that I should reform from now. Regarding the all-caps, you make an interesting point, with an even more interesting explanation. I suspect that your advice regarding the caps is good.
    – thb
    Commented Jun 29, 2012 at 16:41
  • 1
    Not the question, but related. To get after run echo "${TEXT#*x}"
    – Charles L.
    Commented Mar 18, 2016 at 21:41

Not the answer you're looking for? Browse other questions tagged or ask your own question.