5

I have written the following code to search for all the file names in the current working directory that contain the letter 'f'.

for i in *
do
  echo $i
  if [ $i = "*f*" ]
  then
    echo "no"
  else
    echo "yes"
  fi
done

This prints "yes" for every file present in that directory irrespective that it contains 'f' or not. Please help.

4 Answers4

5

[ $i = "*f*" ] splits the file name stored in the variable i into separate words at spaces, interprets each word as a wildcard pattern and expands it if it matches, and then parses the resulting list of words as a condition for [ … ]. To avoid this rigmarole and instead use the file name, put double quotes around the variable expansion.

[ "$i" = "*f*" ] tests whether the file name in the variable i is the three-character name *f*. If you don't have a file with this name, then all files will trigger the else branch, so yes is printed.

In ksh, bash or zsh, you can test whether a string matches a wildcard pattern by using the double bracket construct. Leave the wildcards in the pattern unquoted.

if [[ "$i" = *f* ]]; then …

In plain sh, there is no double bracket syntax. To test whether a string matches a pattern, you can use a case construct.

case "$i" in
  *f*) echo "no";;
  *) echo "yes";;
esac
  • You mentioned in the second para that if we don't have a file there, then all files will trigger the else branch, so yes would be printed. But when the condition will become false, '1' would be returned. After which, in my opinion, the if clause will only work. As a result a "no" would be printed. Isn't this correct? – TheHardRock May 20 '15 at 12:27
  • 2
    @TheHardRock If the condition is false, the else branch is taken. Your code prints no if the then branch is taken and yes if the else branch is taken. Note that in the shell, the status 0 is considered true and a nonzero status is considered false, unlike many other programming languages. – Gilles 'SO- stop being evil' May 20 '15 at 12:31
  • http://imgur.com/u5wMJYY Consider the image over here. When the strings match, zero is returned. If we pass this to an "if" construct (ie. if [ $a = $c ]), then the else part would be triggered. Correct? – TheHardRock May 20 '15 at 16:11
  • No, in this example, if [ $a = $c ] would trigger the “then” clause, since the variables a and c have the same value (and that value doesn't contain any special characters that would make [ $a = $c ] different from [ "$a" = "$c" ]). – Gilles 'SO- stop being evil' May 20 '15 at 16:42
  • 1
    Ok, I got it now. I was with a wrong opinion that if [ 0 ] would trigger the 'else' clause as in the case of 'C'. Thanks. – TheHardRock May 21 '15 at 13:09
1

Since you want to show only files that contain f letter in them, you'd need the continue builtin. For example

for f in *; do if [[ $f != *f* ]]; then continue; else printf '%s\n'  "$f yes"; fi; done

In case you want to show all files with their corresponding yes or no you'd do something like:

for f in *; do if [[ $f = *f* ]]; then printf '%s\n' "$f yes"; else printf '%s\n'  "$f no"; fi; done

The above works for directories too. So if you want to exclude directories you could use ! -d $f for example:

for f in *; do if [[ $f = *f* && ! -d $f ]]; then printf '%s\n' "$f yes"; else printf '%s\n'  "$f no"; fi; done
1

The test ([ is synonym for the "test" builtin) command didn't allow patterns.

    STRING1 = STRING2
                 True if the strings are equal.

so it compare strings letter by letter and sure there is not file with *f* name in your directory (so with your reverse-matching script echoes yes when names didn't match).
Instead of test-buitin or even /bin/test you are free to use bash keyword [[

When the `==' and `!=' operators are used, the string to the right of
the operator is used as a pattern and pattern matching is performed.
When the `=~' operator is used, the string to the right of the operator
is matched as a regular expression.

Both of the next is usable:

[[ "$i" == *f* ]]
[[ "$i" =~ f ]]

But if you just wants to print files from the current directory with f in their names the echo is enough:

echo *f*
Costas
  • 14,916
1

Is this an exercise in iterations? If not the use of find may be easier

search=$(find /path/to/dir -type f -name *f*)
echo $search
0xC0000022L
  • 16,593
jas-
  • 868