如何通过通配 bash 来过滤 STDIN?

How can I filter STDIN by globbing in bash?

我的 bash 脚本通过管道 (stdin) 获取完整路径,并通过命令行参数获取排除模式。目前这处理正则表达式模式,但我想重写以仅处理 glob 模式。

如何使用 glob 模式(如 fnmatch)过滤 stdin 因为据我所知,我不能使用 grep 用于 globbing,我不想手动将 glob 模式翻译为正则表达式。 - 所以我想找到一个快速而不是 hacky 的解决方案。

示例:

echo -e 'apple tree\nbanana tree\norange tree' | ./filter_script.sh '*ban' '*ge*'

预期输出:

# banana tree
# orange tree

完整示例代码在 GitHub Gists

编辑 1:

在现实生活中,此脚本将获得 数千条路径 ,所以我认为,本机 bash 实现不会很好。

有人知道 "grep like" glob 过滤器吗?

假设你只想要一种原生的bash方式来支持glob模式,你可以这样做使用默认设置为 而非 Extended Globs,但可以通过

启用
shopt -s extglob

您有一个按以下方式定义的字符串

myStr=$'apple tree\nbanana tree\norange tree'

您可以按如下方式应用 glob 模式。拳头将字符串读入数组,使用 readarray 按换行符拆分(您需要 bash 4.0 或更大)

readarray -t y <<<"$myStr"

现在循环,

for i in "${y[@]}"; do 
    [[ $i == @(*n*) ]] && echo "$i" ;
done

根据需要生成结果。

其中,@(list)代表Matches one of the given patterns

另一个例子 glob 匹配 apple 中的 *le*(或)orange 中的 ge 将使用

for i in "${y[@]}"; do [[ $i == @(*le*|*ge*) ]] && echo "$i" ; done
apple tree
orange tree

对于您的原始输入,

for i in "${y[@]}"; do [[ $i == @(*ban*|*ge*) ]] && echo "$i" ; done
banana tree
orange tree

对于 OP 将输入参数转换为 glob pattetrns 的要求,由 | 分隔,需要额外的一行来解析位置参数,具体取决于计数,

#!/bin/bash

shopt -s extglob

myStr=$'apple tree\nbanana tree\norange tree'
readarray -t y <<<"$myStr"

# if the argument count is more than 1, since the input arguments are 
# separated by ' ', replace them with `|` as required in the glob
# pattern

(($# > 1)) && args=$(printf "%s" "$*" | tr ' ' '|') || args="$*"

for i in "${y[@]}"; do
    [[ $i == @($args) ]] && echo "$i" ;
done

现在您可以运行将脚本作为

bash script.sh '*ge*' '*le*'
apple tree
orange tree

(或)只有一个参数,

bash script.sh '*ba*'
banana tree

我认为以下应该满足您的需求:

#!/bin/bash
while IFS= read -r line
do
  for pattern in "$@"
  do
    if
      [[ $line = $pattern ]]
    then
      echo "$line"
      break
    fi
  done
done

这会逐行读取标准输入(因此适用于 "stream-type" 应用程序或非常大的文件)。在 Bash 条件 ([[ ]]) 中,相等比较(以及 !=)执行 glob 类型匹配,除非右侧字符串被引用。

受益于 grep 速度的一种方法是将模式转换为正则表达式。

试试这个:

#!/bin/bash
glob_to_regex()
{
  local regex=
  regex=${regex//\./\.}
  regex=${regex/\/\\}
  regex=${regex//\*/.*}
  regex=${regex//\?/.}
  printf %s "^$regex$"
}

regex_from_args()
{
local arg
local not_first=
for arg in "$@"
do
  [[ $not_first ]] && printf %s "|"
  not_first=1
  glob_to_regex "$arg"
done
}

egrep "$(regex_from_args "$@")"