What's the most simple way to extract substring on Unix shell (with regex)?

Simple means:

  • less feature
  • less options
  • less study


I realized regex itself is conflicting with simplicity, and I chose the simplest one cut as the chosen answer. I am sorry for vague question. I changed title to represent current state of this QA more precisely.

  • 2
    It would help if you could describe what you want to extract from where. Even with complex tools like grep and sed, simple things tend to be simple.
    – Sven
    Sep 4, 2010 at 16:14
  • Your question is vague and too broad. Sep 4, 2010 at 17:30
  • The question is quite good! But the marked answer has nothing to do with the question.
    – erikbstack
    May 6, 2014 at 10:26

5 Answers 5


cut might be useful:

$ echo hello | cut -c1,3
$ echo hello | cut -c1-3
$ echo hello | cut -c1-4
$ echo hello | cut -c4-5

Shell Builtins are good for this too, here is a sample script:

# Demonstrates shells built in ability to split stuff.  Saves on
# using sed and awk in shell scripts. Can help performance.

shopt -o nounset
declare -rx       FILENAME=payroll_2007-06-12.txt

# Splits
declare -rx   NAME_PORTION=${FILENAME%.*}     # Left of .
declare -rx      EXTENSION=${FILENAME#*.}     # Right of .
declare -rx           NAME=${NAME_PORTION%_*} # Left of _
declare -rx           DATE=${NAME_PORTION#*_} # Right of _
declare -rx     YEAR_MONTH=${DATE%-*}         # Left of _
declare -rx           YEAR=${YEAR_MONTH%-*}   # Left of _
declare -rx          MONTH=${YEAR_MONTH#*-}   # Left of _
declare -rx            DAY=${DATE##*-}        # Left of _


echo "  Variable: (${FILENAME})"
echo "  Filename: (${NAME_PORTION})"
echo " Extension: (${EXTENSION})"
echo "      Name: (${NAME})"
echo "      Date: (${DATE})"
echo "Year/Month: (${YEAR_MONTH})"
echo "      Year: (${YEAR})"
echo "     Month: (${MONTH})"
echo "       Day: (${DAY})"

That outputs:

  Variable: (payroll_2007-06-12.txt)
  Filename: (payroll_2007-06-12)
 Extension: (txt)
      Name: (payroll)
      Date: (2007-06-12)
Year/Month: (2007-06)
      Year: (2007)
     Month: (06)
       Day: (12)

And as per Gnudif above, there are always sed/awk/perl for when the going gets really tough.

  • While you might sometimes need to declare a variable read-only, it's rare in a context such as this one to need to export a variable. It would be much simpler to just do an assignment: var=value without using declare at all. Sep 4, 2010 at 17:30
  • Wgy is this marked as solution? I don't see the regexes which was the actual question.
    – erikbstack
    May 6, 2014 at 10:26
  • @erikb I chose this for cut, because I put most point on the simplicity more then flexibility. I am sorry for vague question with regex, but I realized the regex itself is conflicts with simplicity.
    – Eonil
    May 6, 2014 at 10:58
  • @Eonil Then would you mind changing the question title? People like me who look for regex solutions will hit this question and don't find a solution.
    – erikbstack
    May 6, 2014 at 11:32
  • 1
    @erikb That sounds reasonable. I did it!
    – Eonil
    May 6, 2014 at 22:01

Unix shells do not traditionally have regex support built-in. Bash and Zsh both do, so if you use the =~ operator to compare a string to a regex, then:

You can get the substrings from the $BASH_REMATCH array in bash.

In Zsh, if the BASH_REMATCH shell option is set, the value is in the $BASH_REMATCH array, else it's in the $MATCH/$match tied pair of variables (one scalar, the other an array). If the RE_MATCH_PCRE option is set, then the PCRE engine is used, else the system regexp libraries, for an extended regexp syntax match, as per bash.

So, most simply: if you're using bash:

if [[ "$variable" =~ unquoted.*regex ]]; then

If you're not using Bash or Zsh, it gets more complicated as you need to use external commands.

  • POSIX bourne shell doesn't, but expr(1) supports
    – ptman
    May 6, 2014 at 10:43
  • 1
    Yes, expr(1) is the obvious example of "use external commands", but it gets "interesting" to safely capture values which can contain arbitrary characters.
    – Phil P
    Jun 20, 2014 at 1:13

Updated Answer - applies to (all?) POSIX compatible expr

Consider also /usr/bin/expr.

$ expr hello : '.\(...\)'

$ expr hello : 'hel'

$ expr hello : '.*el'

$ expr hello : '.*\(el\)'

Original Answer - applies to GNU expr

Consider also /usr/bin/expr.

$ expr substr hello 2 3

You can also match patterns against the beginning of strings.

$ expr match hello h

$ expr match hello hell

$ expr match hello e

$ expr match hello 'h.*o'

$ expr match hello 'h.*l'

$ expr match hello 'h.*e'
  • This works if your version of expr supports substr or match. The BSD based version, e.g., does not. Sep 25, 2022 at 12:36

grep and sed are probably the tools you want, depending on the structure of text.

sed should do the trick, if you do not know what the substring is, but know some pattern that is around it.

for example, if you want to find a substring of digits that starts with a "#" sign, you could write something like:

sed 's/^.*#\([0-9]\+\)/\1/g' yourfile

grep could do something similar, but the question is what you need to do with the substring and whether we are talking normal line-end delimited text or not.

  • This should be marked answer. But I'm not sure why I always need to write \(...\) and you don't seem to need that.
    – erikbstack
    May 6, 2014 at 10:28
  • Offhand, as far as I remember, it is dependant on the type of quotes you use: single or double quotes. Also might have caveats dependant on the shell used (I myself use tcsh).
    – Gnudiff
    Jun 6, 2014 at 5:38
# $1 string
# $2 index of first char
# $3 index of last char
    subStrPatternMatch="\\(.\\{$(( $3 - $2 + 1 ))\\}\\)"
    expr "$1" : "$subStrPatternPrefix$subStrPatternMatch"

Usage example:

string="Hello World!"
s1=$( substr "$string" 0 4 )
s2=$( substr "$string" 6 10 )
s3=$( substr "$string" 11 11 )

echo "$s1"
echo "$s2"
echo "$s3"



You must log in to answer this question.

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