-1
#!/bin/sh

TMP=$(cd Folder && ls)

for name in $TMP; do

  if [[ "${name}" != *"a"* -a ${name} == *"b"* ]] ;then
   echo $name
  fi

done

I was trying to output the names that has no 'a' in it but 'b' instead . This is the error that I got :

./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found
./aufg7.sh: 6: [[: not found

What am I doing wrong here ?

Kusalananda
  • 333,661
Samir
  • 133

2 Answers2

6

To print the filenames in Folder that contain b and not a, in zsh and its ~ (except / and-not) glob operator:

#! /bin/zsh -
set -o extendedglob
print -rC1 -- Folder/(*b*~*a*)(N:t)

(remove the N (for nullglob, if you'd rather want an error be reported where there's no matching file).

In ksh93 (the shell that introduced that [[...]] operator (which is not part of the standard sh syntax) and its & (and) and !(...) (not) operators:

#! /bin/ksh93 -
(
  CDPATH= cd Folder &&
    set -- ~(N)@(*b*&!(*a*)) &&
    (($# > 0)) &&
    printf '%s\n' "$@"
)

With the bash shell (which like zsh has also copied ksh's [[...]] operator), using extglob to support ksh88 glob operators and nullglob to get the effect of zsh's N glob qualifier or ksh93's ~(N) glob operator and some double negation with | (or) to achieve and:

#! /bin/bash -
(
  shopt -s extglob nullglob
  cd ./Folder &&
    set -- !(!(*b*)|*a*) &&
    (($# > 0)) &&
    printf '%s\n' "$@"
)

In standard sh syntax:

#! /bin/sh -
(
  CDPATH= cd Folder || exit
  set -- [*]b[*] *b*
  [ "$1/$2" = '[*]b[*]/*b*' ] && exit # detect the case where the pattern
                                      # expands to itself because there's
                                      # no match as there's no nullglob
                                      # equivalent in sh
  shift
  for i do
    case $i in
      (*a*) ;;
      (*) set -- "$@" "$i"
    esac
    shift
  done
  [ "$#" -gt 0 ] && printf '%s\n' "$@"
)

Note that the solutions that use cd won't work if you have read access to Folder but not search access to it.

More generally, to answer the general question of how to check if a string contains another one in sh, best is with the case construct which is how you do pattern matching in sh. You could even use a helper function:

contains()
  case "$1" in
    (*"$2"*) true;;
    (*) false;;
  esac

To use as if:

if contains foobar o; then
  echo foobar contains o
fi
if ! contains foobar z; then
  echo foobar does not contain o
fi
if
  contains "$file" b &&
    ! contains "$file" a
then
  printf '%s\n' "$file contains b and not a "
fi
5

Your main issue is that you're using a pattern match in [[ ... ]] in a script executed by /bin/sh which does not support these types of tests. See also the question entitled Shell script throws a not found error when run from a sh file. But if entered manually the commands work

The other issue is that you are combining the output of ls into a single string before splitting it into words on spaces, tabs and newlines, and applying filename globbing to the generated words. You then loop over the result of this.

Instead:

#!/bin/sh

cd Folder || exit 1

for name in b; do [ -e "$name" ] || [ -L "$name" ] || continue case $name in (a) ;; (*) printf '%s\n' "$name"; esac done

This loops over all files in the Folder directory that have the letter b in them, and then prints the ones of these that do not contain an a. The test for the a character is done using case ... esac. The *a* pattern matches an a character in the filename if there is one, in which case nothing happens. The printf statements is in the "default case" which gets executed if there is no a in the string.

The tests with -e and -L are there to make sure that we catch the edge case where the loop pattern doesn't match anything. In that case, the pattern will remain unexpanded, so to make sure that we can distinguish between an unexpanded pattern and an actual existing file, we test with -e. The -L test is to catch the further edge case of a symbolic link that has the same name as the loop pattern, but that does not point to anywhere valid. The bash code below does not need this because it uses the nullglob shell option instead (unavailable in /bin/sh).

Running the loop over the expansion of the *b* pattern instead of whatever ls outputs has the benefit that this also is able to deal with filenames containing spaces, tabs, newlines and filename globbing characters (the filenames are never put into a single string that we then try to iterate over).

In the bash shell, you could do the same with a [[ ... ]] pattern matching test, like you tried to do yourself:

#!/bin/bash

cd Folder || exit 1

shopt -s nullglob

for name in b; do [[ $name != a ]] && printf '%s\n' "$name" done

See also:

Kusalananda
  • 333,661