Skip to content
Vidar Holen edited this page Aug 23, 2021 · 2 revisions

expr length has unspecified results. Prefer ${#var}.

or 'expr match' has unspecified results. Prefer 'expr str : regex'.
or 'expr substr' has unspecified results. Prefer 'cut' or ${var#???}.
or 'expr index' has unspecified results. Prefer x=${var%%[chars]*}; $((${#x}+1)).

Problematic code:

# Find length of string
length=$(expr length "$var")

# Match string against regex
expr match "$input" "[0-9]*"

# Find character index in string
pos=$(expr index "$input" ":")

# Get substring by index
col2=$(expr substr "foo    bar  baz" 8 5)

Correct code:

# Find length of string
length=${#var}

# Match string against regex
expr "$input" : "[0-9]*"

# Find character index in string
pos=${input%%:*} pos=$((${#pos}+1))

# Get substring by index (bash)
str="foo    bar  baz"
col2="${str:7:5}"

# Get substring by index (POSIX)
col2="$(printf 'foo    bar  baz\n' | cut -c 8-12)"

Rationale:

You are using a expr with length, match, index, or substr. These forms did not make it into POSIX, and fail on platforms like MacOS and FreeBSD. Consider replacing them with portable equivalents:

length

can be trivially replaced with ${#var}

match

can be trivially replaced with the POSIX form expr str : regex

index

if you only need a numerical index as part of trying to extract a piece of the string, consider replacing it with parameter expansion:

str="mykey=myvalue"
key="${str%%=*}"    # Remove everything after first =, no index required
value="${str#*=}"   # Remove everything before first =, no index required

otherwise, you can find the index of the first = using parameter expansion and string length:

str="mykey=myvalue"
x=${str%%=*}     # Assign x="mystr"
index=$((${#x}+1))   # Add 1 to length of x

substr

Extract a substring via character index is generally fragile. For example, in this example, any minor changes to the format, including just the version increasing from 8.9 to 8.10, will cause the following snippet to fail:

str="VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Feb 15 2021 12:29:39)"
version=$(expr substr "$str" 19 3)

Instead, consider a different approach:

x="${str%% (*}"     # Delete ` (` and everything after, giving "VIM - Vi IMproved 8.2"
version="${x##* }"  # Delete everything before last space, giving "8.2"

# Get the fifth word separated by spaces
IFS=" " read -r _ _ _ _ version _ << EOF
$str
EOF

If you still want to use character index, this is trivially done in Bash/Ksh with ${var:offset:length} (0-based).

In POSIX, you can generally use cut, though be careful if the value can contain multiple lines.

Exceptions:

If you know your script will only run on platforms where these forms are supported, like GNU or BusyBox, you can ignore this warning.

Related resources:

  • Help by adding links to BashFAQ, StackOverflow, man pages, POSIX, etc!

ShellCheck

Each individual ShellCheck warning has its own wiki page like SC1000. Use GitHub Wiki's "Pages" feature above to find a specific one, or see Checks.

Clone this wiki locally