Powłoka: sprawdzanie, czy ciąg zawiera określony znak [duplikat]

Dec 22 2020
#!/bin/sh

TMP=$(cd Folder && ls) for name in $TMP; do

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

done

Próbowałem wypisać nazwy, w których nie ma „a”, ale zamiast tego „b”. Oto błąd, który otrzymałem:

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

Co ja tu robię źle?

Odpowiedzi

5 Kusalananda Dec 22 2020 at 02:19

Twoim głównym problemem jest to, że używasz dopasowania do wzorca w [[ ... ]]wykonywanym skrypcie, /bin/shktóry nie obsługuje tego typu testów. Zobacz także pytanie zatytułowane Skrypt powłoki zgłasza błąd „not found” po uruchomieniu z pliku sh. Ale jeśli zostaną wprowadzone ręcznie, polecenia działają

Inną kwestią jest to, że łączysz dane wyjściowe programu lsw jeden ciąg przed podzieleniem go na słowa ze spacjami, tabulatorami i znakami nowej linii oraz zastosowaniem globalizacji nazw plików do wygenerowanych słów. Następnie zapętlasz wynik tego.

Zamiast:

#!/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

To zapętla wszystkie pliki w Folderkatalogu, które zawierają literę b, a następnie wypisuje te, które nie zawierają a. Test apostaci wykonuje się za pomocą case ... esac. *a*Wzór pasuje do aznaku w nazwie pliku, jeśli jest tylko jedna, w tym przypadku nic się nie dzieje. Te printfoświadczenia jest w przypadku „domyślnym”, który zostanie wykonany, jeśli nie jest aw struny.

Testy z -ei -Lsą tam, aby upewnić się, że złapiemy sprawę krawędź, gdzie wzór pętla niczego nie pasujące. W takim przypadku wzorzec pozostanie nierozwinięty, więc aby upewnić się, że możemy odróżnić nierozwinięty wzorzec od istniejącego pliku, testujemy za pomocą -e. -LTest jest złapać kolejny przypadek krawędzi dowiązania symbolicznego, który ma taką samą nazwę jak wzór pętli, ale to nie wskazuje na dowolnym ważny. Poniższy bashkod nie potrzebuje tego, ponieważ nullglobzamiast tego używa opcji powłoki (niedostępna w /bin/sh).

Uruchamianie pętli nad rozwinięciem *b*wzorca zamiast jakichkolwiek danych lswyjściowych ma tę zaletę, że jest w stanie poradzić sobie również z nazwami plików zawierającymi spacje, tabulatory, znaki nowej linii i znaki globbingu (nazwy plików nigdy nie są umieszczane w jednym ciągu iteruj).

W bashpowłoce możesz zrobić to samo z [[ ... ]]testem dopasowania wzorców, tak jak próbowałeś zrobić sam:

#!/bin/bash

cd Folder || exit 1

shopt -s nullglob

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

Zobacz też:

  • Dlaczego mój skrypt powłoki dławi się białymi znakami lub innymi znakami specjalnymi?
  • Kiedy konieczne jest podwójne cytowanie?
  • Dlaczego * nie * przeanalizować `ls` (i co zrobić zamiast tego)?
  • Dlaczego printf jest lepszy niż echo?
6 StéphaneChazelas Dec 22 2020 at 02:35

Aby wydrukować nazwy plików, Folderktóre zawierają bi nie a, operator in zshi ~( z wyjątkiem / i nie ) glob:

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

(usuń N(dla nullglob, jeśli wolisz zgłaszać błąd w przypadku braku pasującego pliku).

W ksh93(powłoce, która wprowadziła ten [[...]]operator (który nie jest częścią standardowej shskładni) oraz jego &( i ) i !(...)( nie ) operatory:

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

Dzięki bashpowłoce (który podobnie jak zshrównież kopiowane KSH za [[...]]operatora), używając extglobdo wspierania ksh88 operatorów glob i nullglobaby uzyskać efekt zsh za Nglob kwalifikacjach lub ksh93„s ~(N)operatora glob i jakąś podwójną negację z |( lub ), aby osiągnąć i :

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

W standardowej shskładni:

#! /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' "$@"
)

Zwróć uwagę, że rozwiązania, które używają cd, nie będą działać, jeśli masz dostęp do odczytu, Folderale nie masz dostępu do wyszukiwania.

Mówiąc bardziej ogólnie, aby odpowiedzieć na ogólne pytanie, jak sprawdzić, czy ciąg zawiera inny ciąg w sh, najlepiej jest skorzystać z casekonstrukcji, która polega na dopasowaniu wzorca sh. Możesz nawet użyć funkcji pomocniczej:

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

Aby używać tak, jakby:

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