I want to test whether a line, read in from a file, has a specific beginning AND an ending containing a word held in a variable. Here's some code:

The input file is:

line one
#; line two
#; line three blah
line four

A minimal script, which fails, is:

declare ENDOFLINE= "blah"
exec 3< "inputfile"

while read LINE <&3
    if [[ ("$LINE" =~ "^#;") && (( ("$LINE" =~ "${ENDOFLINE%$}") )) ]]; 
    echo score!
    else echo no score

But, if I do:

if [[ ("$LINE" =~ "^#;") && (( ("$LINE" =~ "blah$") )) ]];

it succeeds in identifying the correct line (=> #; line three blah). In other words, I need a compound test condition where the first test is whether the beginning of the line is '#;' and the end of the line is a string contained in the variable $ENDOFLINE .

Thanks for any help.

  • I'm not familiar with "MWE script". And Google isn't showing anything that seems likely. Can you explain that? – mattdm Apr 6 '11 at 20:27
  • That said, there's no reason to make this two regexes, is there? – mattdm Apr 6 '11 at 20:32
  • @mattdm - MWE = 'minimum working example'; Code simplified from my actual script containing the essence of the problem for faster understanding. Most sites where code is discussed require an MWE example. – bev Apr 6 '11 at 20:36
  • 1
    @bev: Don't use non-standard abbreviations if you want to be understood. You should have said which shell you are using (there are three, not counting minor variants, that your script could be in). “MWE” was where one would have expected something like “bash” or “ksh”, which was confusing. – Gilles 'SO- stop being evil' Apr 6 '11 at 20:40
  • 1
    @mattdm: You might want two regexps for clarity, or because there's a risk that the prefix and suffix overlap. – Gilles 'SO- stop being evil' Apr 6 '11 at 20:41

Looks like a simple mistake: ${ENDOFLINE%$} strips a $ at the end of $ENDOFLINE, but what you want to do is use $ENDOFLINE literally and have a $ after it to indicate the end of the line.

if [[ ("$LINE" =~ "^#;") && (( ("$LINE" =~ "${ENDOFLINE}$") )) ]];

This works in zsh but not in ksh or bash. Bash requires the regexp $ to be unquoted (otherwise, it's interpreted literally), and ksh doesn't like double parentheses inside [[ … ]] (it interprets them as an arithmetic instruction). This simpler line works in all three shells (note that [[…]] is one of the few places where you don't need "…" around variable expansions):

if [[ $LINE =~ ^"#;" && $LINE =~ $ENDOFLINE$ ]];

If $ENDOFLINE never overlaps with #; (e.g. if ENDOFLINE is ;foo, you'll be accepting #;;foo but rejecting #;foo), you can reduce this to a single test:

if [[ $LINE =~ ^"#;".*$ENDOFLINE$ ]];

You can also use a wildcard pattern instead of a regexp here:

if [[ $LINE = "#;"*"$ENDOFLINE" ]];

The [[…]] construct doesn't exist in all shells, it's specific to bash, ksh and zsh. On other shells (Bourne, dash, anything POSIX), you can do wildcard matches with the case construct:

case $LINE in
  "#;"*"$ENDOFLINE") echo score;;
  *) echo no score;;
  • Thanks so much. Your 'simpler' version is what I need. Thanks also for the instruction on the syntax of [[...]] . I was unable to find an answer to my question online, and I had tried many variants of the code I posted here. I'm afraid my understanding of regexp in the various languages I code in is frequently found wanting. – bev Apr 6 '11 at 21:03
  • I appreciate the additions to your original answer, both the 'one test' expression and the wildcard test work in my script, and the wildcard answers another of my questions. I hadn't realized that a wildcard was possible in that context. The 'case' example seems obvious, but I hadn't thought of it. I have another regexp question, since this was was such a success, which I will post in a minute. – bev Apr 6 '11 at 22:57

You can also do this using grep:

while read line; do
    if grep -Eq "^#;.*$end$" <<< "$line"; then
        echo score!
        echo no score

(That's in bash; the syntax may be slightly different in other shells but the principle is the same.)

  • 2
    Good point about grep, but not if you're going to use it in a loop. Replace the whole loop with grep -q "^#;.*$end\$" <&3. – Gilles 'SO- stop being evil' Apr 6 '11 at 21:58
  • Thanks for your suggestion. I'm putting your answer into my notes file for future reference. Another solution I hadn't considered... – bev Apr 6 '11 at 23:01

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.