What does "${line#*'Caused By'}" != "$line" mean in a shell script?
${line#*'Caused By'}
is a specific instance of the variable substitution ${parameter#word}
(as it's written in the bash
manual, and also in the POSIX standard for the sh
shell).
In ${parameter#word}
, the pattern word
will be removed from the beginning of the value of $parameter
. It's called "Remove Smallest Prefix Pattern" because it will remove the shortest matching prefix string that matches the pattern in word
(with ##
in place of #
it removes the longest matching prefix string).
It this specific example, the string Caused by
(and anything before it, thanks to the *
) is, if it exists, removed from the value of $line
. The single quotes around the string are redundant.
By comparing the result of the substitution with the value of the variable itself, the test determines whether the value of $line
contains the text Caused by
, and prints Yes
if it does.
This has the same effect as
if [[ "$line" == *'Caused by'* ]]; then
echo 'Yes'
fi
in bash
, ksh93
or zsh
, or
case "$line" in
*'Caused by'*) echo 'Yes'
esac
in any sh
shell.
The loop in the question reads "lines" from standard input. See the question "Understanding "IFS= read -r line" " for a discussion about this.
The left-hand side of the if-condition uses the pattern matching functionality of bash. The matched string will be removed if it includes the 'Caused By'. The line will no longer be identical to what it was before and therefore it will not trigger the if-clause.
Here is an example that you can run on the shell:
echo -e "Number 1 Caused by me.\nNumber 2 is normal.\n" |
while read line; do
echo "${line#*'Caused by'}"
done
Result:
me.
Number 2 is normal.
Action (or execution in this case) always speaks louder, so let's look at what this script does when executed (excuse the liberty taken to make the output more verbose):
while read -r line
do
if [ "${line#*'Caused by'}" != "$line" ]; then
echo "Line contains string Caused by"
else
echo "Line does not contain string Caused by"
fi
done
Input: String with Caused by
Output: Line contains string Caused by
Input: Just a normal string
Output: Line does not contain string Caused by
The pattern matching used in this script "${line#*'Caused by'}
is replacing all string (owing to the wildcard *
) from the beginning to the end of Caused by in the inputted line and then it compares it with the original $line
parameter to see whether they are equal or not. Simple stated, all it does is a check whether the line contains the string Caused by. Finally it prints Line contains string Caused by if the line does contain Caused by.
Now, a few words about the shell parameter expansion for the ${parameter#word}
format with some examples:
If the pattern matches the beginning of the value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the "#'' case) or the longest matching pattern (the "##'' case) deleted.
$ test=aabbcc
$ echo ${test#*bb}
$ cc
$ test=aabbcc
$ echo ${test#a*b}
$ bcc
An example of the longest matching pattern format:
$ test=aabbcc
$ echo ${test##a*b}
$ cc
Reference: man bash
: ${parameter#word}