如何通过bash中的globbing过滤STDIN?

时间:2017-04-11 11:35:57

标签: bash glob

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

如何使用glob模式过滤stdin(例如fnmatch)?因为据我所知,我无法使用grep进行globbing ,我不想手动将glob模式转换为regexp。 - 所以我想找到一个快速而不是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过滤器?

3 个答案:

答案 0 :(得分:1)

假设您希望以原生bash方式支持glob模式,您可以使用Extended Globs来实现默认情况下设置,但可以通过

启用
shopt -s extglob

您有以下列方式定义的字符串

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

您可以按如下方式应用glob模式。 Fist将字符串读入数组,使用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*le*(或)apple中的ge的另一个示例orange将使用

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

答案 1 :(得分:0)

我认为以下内容可以满足您的需求:

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

这逐行读取标准输入(因此可以在&#34;流式&#34;应用程序或非常大的文件中使用)。在Bash条件([[ ]])中,相等比较(以及!=)执行glob-type匹配,除非引用右侧字符串。

答案 2 :(得分:0)

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

试试这个:

#!/bin/bash
glob_to_regex()
{
  local regex=$1
  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 "$@")"