8

Can someone explain what this means in a shell script?

while read -r line
do 
  if [ "${line#*'Caused By'}" != "$line" ]; then
    echo "Yes"
  fi
done       
12

${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.

6

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.
3

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}

  • Got it, thanks for the clarification! Editing the answer to speak about ${parameter#word} – Curious Feb 13 '18 at 19:02
  • @Jesse_b can you review it now please? – Curious Feb 13 '18 at 19:32
  • Seems correct but not much different from Kusalananda's answer. – Jesse_b Feb 13 '18 at 20:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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