【问题标题】:If xargs is map, what is filter?如果 xargs 是地图,那么过滤器是什么?
【发布时间】:2014-09-18 16:49:15
【问题描述】:

我认为xargs 是UNIX shell 的映射函数。 filter 函数是什么?

编辑:看来我必须更明确一点。

假设我必须处理一个接受单个字符串作为参数并返回退出代码 0 或 1 的程序。该程序将充当它接受的字符串的谓词。

例如,我可能决定将字符串参数解释为文件路径,并将谓词定义为“此文件是否存在”。在这种情况下,程序可以是test -f,给定一个字符串,如果文件存在则以 0 退出,否则以 1 退出。

我还必须交出一串字符串。例如,我可能有一个文件 ~/paths 包含

/etc/apache2/apache2.conf
/foo/bar/baz
/etc/hosts

现在,我想创建一个新文件 ~/existing_paths,其中仅包含我的文件系统中存在的那些路径。在我的情况下,那将是

/etc/apache2/apache2.conf
/etc/hosts

我想通过读入~/paths 文件、通过谓词test -f 过滤这些行并将输出写入~/existing_paths 来做到这一点。与xargs 类比,如下所示:

cat ~/paths | xfilter test -f > ~/existing_paths

这是我正在寻找的假设程序xfilter

xfilter COMMAND [ARG]...

对于其标准输入的每一行L,将调用COMMAND [ARG]... L,如果退出代码为0,则打印L,否则不打印任何内容。

明确地说,我不是在寻找:

  • 一种按存在过滤文件路径列表的方法。这是一个具体的例子。
  • 如何编写这样的程序。我可以做到。

正在寻找:

  • 预先存在的实现,例如xargs,或
  • 清楚地解释为什么不存在

【问题讨论】:

  • 因为无效fork N 次命令只会根据退出状态进行归档。正如您所说,用任何语言(bash、perl、C)编写都很简单——但效果不佳。更有效的是直接使用一些命令(正确的工具 - 基于情况)可以读取 STDIN过滤输入 为 zilion 次 fork/exec 退出状态命令。很多时候xargs 也不是最有效的方法。 (想象一个长 1_000_000 行的文件列表。Milion forks 并不是你能做的最好的事情......)如果需要这样的东西(如你所知),它是一个 3 行 bash 函数
  • @jm666 听起来“有效”是指“表现出色”。我不在乎表现,我在乎表现力。

标签: shell map filter xargs


【解决方案1】:

如果地图是xargs,过滤器是......仍然是xargs

示例:列出当前目录下的文件,过滤掉不可执行的文件:

ls | xargs -I{} sh -c "test -x '{}' && echo '{}'"

这可以通过(非生产就绪)功能变得方便:

xfilter() {
    xargs -I{} sh -c "$* '{}' && echo '{}'"
}
ls | xfilter test -x

或者,您可以通过 GNU Parallel 使用并行过滤器实现:

ls | parallel "test -x '{}' && echo '{}'"

【讨论】:

    【解决方案2】:

    所以,您正在寻找:

     reduce(  compare(  filter( map(.. list()) ) ) )
    

    可以改写成什么

     list | map | filter | compare | reduce
    

    bash 的主要功能是流水线,因此不需要一个特殊的filter 和/或reduce 命令。事实上,几乎所有的 unix 命令都可以在一个(或多个)函数中发挥作用:

    • 列表
    • 地图
    • 过滤器
    • 减少

    想象一下:

    find mydir -type f -print | xargs grep -H '^[0-9]*$' | cut -d: -f 2 | sort -nr | head  -1
    ^------list+filter------^   ^--------map-----------^   ^--filter--^   ^compare^  ^reduce^
    

    创建测试用例:

    mkdir ./testcase
    cd ./testcase || exit 1
    for i in {1..10}
    do
        strings -1 < /dev/random | head -1000 > file.$i.txt
    done
    mkdir emptydir
    

    你会得到一个名为testcase的目录,在这个目录中有10个文件和一个目录

    emptydir  file.1.txt  file.10.txt file.2.txt  file.3.txt  file.4.txt  file.5.txt  file.6.txt  file.7.txt  file.8.txt  file.9.txt
    

    每个文件包含 1000 行随机字符串,有些行只包含数字

    现在运行命令

    find testcase -type f -print | xargs grep -H '^[0-9]*$' | cut -d: -f 2 | sort -nr | head -1
    

    您将从每个文件中获得最大的数字行,例如:42。 (当然这样可以更有效,这里只做demo)

    分解:

    find testcase -type f -print 将打印每个普通文件,因此,LIST(并且仅简化为文件)。输出:

    testcase/file.1.txt
    testcase/file.10.txt
    testcase/file.2.txt
    testcase/file.3.txt
    testcase/file.4.txt
    testcase/file.5.txt
    testcase/file.6.txt
    testcase/file.7.txt
    testcase/file.8.txt
    testcase/file.9.txt
    

    xargs grep -H '^[0-9]*$' as MAP 将为列表中的每个文件运行grep 命令。 grep 通常用作 filter,例如:command | grep,但现在(使用 xargs)将输入(文件名)更改为(仅包含数字的行)。输出,多行如:

    testcase/file.1.txt:1
    testcase/file.1.txt:8
    ....
    testcase/file.9.txt:4
    testcase/file.9.txt:5
    

    行结构:filename colon number,只需要数字,因此调用纯过滤器,从每行cut -d: -f2 中去除文件名。它输出许多行,例如:

    1
    8
    ...
    4
    5
    

    现在reduce(得到最大的数字),sort -nr 对所有数字进行数字排序和倒序(desc),所以它的输出是这样的:

    42
    18
    9
    9
    ...
    0
    0
    

    head -1 打印第一行(最大的数字)。

    当然,您可以使用bash 编程结构(循环、条件等)直接编写自己的列表/过滤器/映射/归约函数,或者您可以使用任何成熟的脚本语言,如perl,特殊语言如awksed“语言”或dc(rpn)等等。

    有一个特殊过滤器命令,例如:

    list | filter_command cut -d: -f 2
    

    很简单不需要,因为可以直接使用

    list | cut
    

    【讨论】:

      【解决方案3】:

      您可以让awk 执行filterreduce 功能。

      过滤器:

      awk 'NR % 2 { $0 = $0 " [EVEN]" } 1'
      

      减少:

      awk '{ p = p + $0 } END { print p }'
      

      【讨论】:

      • 谢谢,但是xargs 有一个通用shell 命令作为参数,它是映射输入行的函数。以此类推,filter 应该使用 shell 命令作为输入行的谓词(例如,基于其返回码是否为 0)。在您的示例中,谓词仅在 awk-speak 中定义。
      • @jameshfisher 我猜你需要的是外壳。
      • 我不确定你的意思——比如this?
      • @jameshfisher bash 中的循环类似于while IFS= read -r line; do ...; done。你可以使用所有你想要的变量让它按照你想要的方式工作,并使用$?if 条件或逻辑运算符&amp;&amp;|| 来控制逻辑。
      【解决方案4】:

      作为一名长期的函数式程序员,我完全理解您的问题,这里是答案:Bash/unix 命令流水线并不像您希望的那样干净。

      在上面的例子中:

      find mydir -type f -print | xargs grep -H '^[0-9]*$' | cut -d: -f 2 | sort -nr | head  -1
      ^------list+filter------^   ^--------map-----------^   ^--filter--^   ^compare^  ^reduce^
      

      更纯粹的形式如下:

      find mydir | xargs -L 1 bash -c 'test -f $1 && echo $1' _ | grep -H '^[0-9]*$' | cut -d: -f 2 | sort -nr | head -1
      ^---list--^^-------filter---------------------------------^^------map----------^^--map-------^  ^reduce^
      

      但是,例如,grep 也具有过滤功能:grep -q mypattern,如果它与模式匹配,则简单地返回 0。

      要获得更像您想要的东西,您只需定义一个过滤器 bash 函数并确保将其导出以便与 xargs 兼容

      但是你会遇到一些问题。就像,测试有二元和一元运算符。您的过滤器功能将如何处理这个问题?手,对于这些情况,您会决定输出 true 什么?不是无法克服,而是很奇怪。假设只有一元操作:

      filter(){
          while read -r LINE || [[ -n "${LINE}" ]]; do
              eval "[[ ${LINE} $1 ]]" 2> /dev/null && echo "$LINE"
          done
      }
      

      所以你可以做类似的事情

      seq 1 10 | filter "> 4"
      5
      6
      7
      8
      9
      

      写这篇文章的时候很喜欢

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-29
        • 1970-01-01
        • 1970-01-01
        • 2021-08-04
        • 2011-08-17
        • 1970-01-01
        相关资源
        最近更新 更多