【问题标题】:Removing stopwords from a string without extra/unnecessary looping [duplicate]从字符串中删除停用词而无需额外/不必要的循环[重复]
【发布时间】:2018-08-30 15:23:30
【问题描述】:

我正在尝试清理我的字符串并删除特定的单词。我有一段可以工作的代码,但它不漂亮也不健壮。

输入:the_for_an_apple_this

删除单词:the、for、an

输出:apple_this

#!/bin/bash
str="the_for_an_apple_this"
echo $str

# looping is down because after the awk gsup the next match wouldn't work 
counter=0
while [ $counter -le 10 ] 
do
    # replace with , "_" ?? is this correct, it seems to work
    str=`echo $str | awk '{gsub(/(^|_)(the|for|an)($|_)/,"_")}1'`
    ((counter++))
    echo $str
done

# remove beginning or trailing _
str=`echo $str | awk '{gsub(/(^)_/,"")}1' | awk '{gsub(/_($)/,"")}1'`
echo $str
  1. 这是一个好方法吗? (我使用 awk 是因为我需要最好的跨平台兼容性,而 sed 会出现问题)
  2. 如何替换我的 while 条件,以便在不再发生匹配时停止。

此处可测试版本http://rextester.com/BHYSP47270

如何在没有易碎计数器的情况下清理它并使其正常工作?

【问题讨论】:

  • 还要注意,最好只使用一个awk 脚本来处理您的整个输入,而不是使用awk 的单独副本来处理每一行。 awk 比 bash 快,但是如果你继续启动它,运行一条线并一遍又一遍地关闭它,你不会从那个性能增量中得到任何好处——事实上,你会得到很多东西比原生 bash 字符串操作慢倍。
  • 不过,谈到您的真实用例——您需要保留订单吗?一般来说,集合算术是comm的工作;见BashFAQ #36

标签: regex shell awk


【解决方案1】:

仅使用本机 bash 逻辑:

#!/bin/bash
remove_stopwords() {
  local old_settings=$-  # store original shell settings so we can undo set -f
  local -a words=( )     # create "words" array as a local variable
  local IFS=_            # set the underscore to be the only character than separates words
  set -f                 # disable globbing to make unquoted expansion safe

  for word in $1; do     # split str on chars in IFS (underscores) and iterate
    case $word in "the"|"for"|"an") continue;; esac  # skip stopwords
    words+=( "$word" )   # put words we didn't skip into our array
  done
  echo "${words[*]}"     # join words with underscores (first IFS character) and echo

  if ! [[ $old_settings = *f* ]]; then set +f; fi # undo "set -f"
}

str="the_for_an_apple_this"
remove_stopwords "$str"

你可以在https://ideone.com/hrd1vA看到这个运行


或者,更简洁:在子shell中运行函数体。还编辑为使用更多仅限 bash 的功能

remove_stopwords() (     # parentheses launch a subshell
    words=( )
    IFS=_
    set -f               # disable globbing
    for word in $1; do   # unquoted for word splitting
        [[ $word == @(the|for|an) ]] || words+=( "$word" )
    done
    echo "${words[*]}"
)

【讨论】:

  • 不错。我使用关联数组来保存要删除的单词变得越来越复杂。
  • 我先写了一个关联数组版本,然后决定不想处理 bash 4.0 的要求,所以在发帖前又回去做了。
  • 谢谢!!!如何撤消 set -f 设置以及如何撤消 IFS 设置?我想确保当我的方法返回时不会影响正在运行的脚本的其余部分
  • set +f 撤消 set -f。对 IFS 更改最简单的方法是将其范围限定为子 shell:在全局更改 IFS 之前在代码中放置 (,在完成需要该值后放置 )。也可以将其更改为函数局部变量;我可以编辑以显示。
  • 如果您使用括号定义函数,它将在子shell中运行,因此您不必本地化 IFS 或记住 shell 设置:remove_stopwords() ( ... )
【解决方案2】:

单独使用 awk 怎么样?

$ tail file1 file2
==> file1 <==
an_for_the

==> file2 <==
the_for_an_apple_this
$ awk 'BEGIN{RS=ORS="_"} NR==FNR{r[$1];next} ($1 in r){next} 1' file1 file2
apple_this

这会读取您的“排除”字符串(存储在file1),将由下划线分隔的单词存储为数组中的索引。然后,它会逐步遍历您的输入字符串(存储在 file2 中),使用相同的记录分隔符,跳过上一步中创建的数组成员的记录。

可能需要对行尾进行一些微调。

【讨论】:

    【解决方案3】:

    你可以简单地使用 bash 来做到这一点:

    shopt -s extglob
    str="the_for_an_apple_this"
    for words in "the" "for" "an"; do
       str=${str//$words/}
    done
    str=${str//+(_)/_}; str=${str#_}; str=${str%_}
    

    如果你使用循环可以移除:

    shopt -s extglob
    str="the_for_an_apple_this"
    str=${str//@(the|for|an)/}
    str=${str//+(_)/_}; str=${str#_}; str=${str%_}
    

    在这个解决方案中,我们使用了源自 KSH 的扩展 glob 选项:

    【讨论】:

    • 太好了,谢谢!如何撤消 shopt -s 设置?我想确保当我的方法返回时不会影响正在运行的脚本的其余部分
    • shopt -s extglob 设置选项,shopt -u extglob 取消设置。
    • str="the_for_plan_apple_this" = 哎呀。
    • @PesaThe 我可以争辩说 OP 没有指定他要删除的单词应该前后加下划线。但话又说回来,你是完全正确的。
    【解决方案4】:

    为了好玩,一个 perl 版本:

    perl -lne '
        %remove = map {$_=>1} qw(the for an);
        print join "_", grep {$_ and not $remove{$_}} split /_/;
    ' <<< "the_for_an_apple__the_this_for"
    
    apple_this
    

    或者不区分大小写的版本

    perl -lne '
        %remove = map {uc,1} qw(the for an);
        print join "_", grep {$_ and not $remove{+uc}} split /_/;
    ' <<< "tHe_For_aN_aPple__thE_This_fOr"
    

    aPple_This
    

    【讨论】:

      猜你喜欢
      • 2015-08-06
      • 2021-01-13
      • 2017-12-06
      • 2020-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多