Shell:检查字符串是否包含指定字符

Shell:检查字符串是否包含指定字符
#!/bin/sh

TMP=$(cd Folder && ls)

for name in $TMP; do

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

done

我试图输出其中没有“a”但有“b”的名称。这是我得到的错误:

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

我在这里做错了什么?

答案1

要打印包含且不Folder包含、 in及其(bazsh~除了/并不是) 全局运算符:

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

(删除N(对于nullglob,如果您希望在没有匹配文件的情况下报告错误)。

在(引入该运算符(不是标准语法的一部分)ksh93的 shell及其([[...]]sh&) 和!(...)不是) 运算符:

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

使用bashshell(它zsh也复制了 ksh 的[[...]]运算符),使用extglob支持 ksh88 glob 运算符并nullglob获得 zsh 的Nglob 限定符或ksh93~(N)glob 运算符以及一些双重否定|(或者) 实现:

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

在标准sh语法中:

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

请注意,如果您具有读取权限但没有搜索权限,则使用的解决方案cd将不起作用。Folder

更一般地说,要回答如何检查字符串中是否包含另一个字符串的一般问题sh,最好是case使用在 中进行模式匹配的构造sh。您甚至可以使用辅助函数:

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

使用就像:

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

答案2

[[ ... ]]您的主要问题是您在执行的脚本中使用模式匹配,/bin/sh而该脚本不支持这些类型的测试。另请参阅标题为的问题从 sh 文件运行时,Shell 脚本会引发未找到错误。但如果手动输入命令就可以工作

另一个问题是,您将 的输出组合ls成单个字符串,然后将其拆分为空格、制表符和换行符上的单词,并对生成的单词应用文件名通配。然后你循环遍历这个结果。

反而:

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

这将循环遍历Folder目录中包含该字母的所有文件b,然后打印其中不包含a.角色的测试a是使用 完成的case ... esac。该*a*模式与文件名中的某个字符匹配a(如果有),在这种情况下什么也不会发生。这些语句处于“默认情况”,如果字符串中printf没有,则执行该语句。a

-e使用和进行测试-L是为了确保我们捕获循环模式与任何内容都不匹配的边缘情况。在这种情况下,模式将保持未展开状态,因此为了确保我们可以区分未展开的模式和实际存在的文件,我们使用-e.该-L测试是为了捕获与循环模式同名的符号链接的进一步边缘情况,但它不指向任何有效的地方。下面的代码bash不需要这个,因为它使用nullglobshell 选项(在 参考资料中不可用/bin/sh)。

在模式扩展上运行循环*b*而不是任何ls输出的好处是,它还能够处理包含空格、制表符、换行符和文件名通配符的文件名(文件名永远不会放入我们随后尝试的单个字符串中)迭代)。

bashshell 中,您可以使用模式匹配测试执行相同的操作[[ ... ]],就像您自己尝试做的那样:

#!/bin/bash

cd Folder || exit 1

shopt -s nullglob

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

也可以看看:

相关内容