2
cat list.txt | grep '^http://.*\.osm\..*$' | while read line; do
    fn=$(basename $line)
    do_something()
done
# TODO: check if it did something

In case the grep command returns nothing, it won't enter the loop and do_something() won't be executed.

I can't check what's in $fn outside of the while loop, see Bash variable scope.

What's the least invasive solution for checking if do_something() was executed here?

Community
  • 1
  • 1
didi_X8
  • 5,018
  • 10
  • 42
  • 46

5 Answers5

4

You can use a flag whose value will be changed if the loop is executed. Below is a poc.

shopt -s lastpipe   # bash 4.2 or greater only
flag="false"
cat list.txt | grep '^http://.*\.osm\..*$' | while read line; do
    fn=$(basename $line)
    do_something()
    flag="true"
done

if [ "$flag" = "true" ]
then
    echo "loop was executed"
fi

You need to use the following if while spans a sub-shell (thanks to all who commented below)

 while read line
   do
       fn=$(basename $line)
       do_something()
       flag="true"
   done < <(grep '^http://.*\.osm\..*$' list.txt)
Bill
  • 5,263
  • 6
  • 35
  • 50
  • 2
    Will that really work? The body of the while loop runs in a subshell, so I would have thought changes, even to existing variables are not propagated back up. – Adrian Ratnapala May 14 '13 at 16:08
  • 1
    That doesn't work because of the different scope, see the link in my description – didi_X8 May 14 '13 at 16:12
  • 3
    That depends on the particular shell. Assuming recent versions of `bash`, which seem to be most common, it will not. Older versions of, e.g. `ksh` will work, though... – twalberg May 14 '13 at 16:13
3

Since the while loop runs in a sub shell, it cannot propagate values back up to the parent shell. There are still ways to do what is wanted, though. Here's one:

if grep -q '^http://.*\.osm\..*$' list.txt       # loop will definitely run
then
  while read line
  do
    # do something with line
  done < <(grep '^http://.*\.osm\..*$' list.txt)
else                                             # no matches, loop will not run
  # do something else
fi

It has the side effect of running grep twice, which could be avoided by saving the output of grep and post-processing it, as suggested in another answer, but in some ways this is a bit simpler to understand...

twalberg
  • 59,951
  • 11
  • 89
  • 84
2

Save the output of grep in a variable and test it explicitly.

filtered=$(cat list.txt | grep ....)

if [ -z "$filtered" ] ;
  ... handle empty output ..
 else
  ... do your while loop here... 
fi

BTW: newlines are preserved inside the "$filtered", but be sure to quote it when you use it.

Adrian Ratnapala
  • 5,485
  • 2
  • 29
  • 39
2

You can use a process substitution in place of the pipeline, which would allow a flag to be usefully set:

flag=0
while read line; do
    flag=1
    fn=$(basename $line)
    do_something()
done < <( grep '^http://.*\.osm\..*$' list.txt )

if [[ $flag == 1 ]]; then
   ...
fi
chepner
  • 497,756
  • 71
  • 530
  • 681
1

A variation on @chepner solution:

flag=0
while read line; do
    grep -q '^http://.*\.osm\..*$' <<< $line && {
      flag=1
      fn=$(basename $line)
      do_something()
    }
done < list.text

if [[ $flag == 1 ]]; then
   ...
fi

Use then one that better fits your need.

Atle
  • 5,299
  • 1
  • 15
  • 10