将命令应用于列表

将命令应用于列表

很多时候,我需要将某个简单的函数应用于列表(或者更准确地说,应用于一个字符串,其中我想要将其视为单独项目的子字符串由新行分隔)。假设我需要从包含某个其他字符串的文件的文件名列表中提取某些数字,例如stringToBeSearched.获取适当的文件名列表的一个简单解决方案是

grep -l "stringToBeSearched" *

然后我只想将其提供给另一个接受我想要的子字符串的函数。为了尝试做到这一点,我定义了例如

 f () { echo $(sed 's/begin-\([0-9]*\).end/\1/' <<<$1) ;}

它应该提取例如格式的文件中的数字begin-123.end。我已经宁愿避免定义这样的函数,因为它不会被重用,但我似乎找不到 Mathematica 中所谓的纯函数的等价物,即形式为 #1 +#2 的东西& 用于将两个参数添加在一起的匿名函数。

应用于字符串时,该函数可以实现我想要的功能,因此剩下的唯一步骤是将其应用于正确的字符串列表。我想我可以使用

  grep -l "stringToBeSearched" * | xargs -n1 f

只是这似乎不起作用,因为 xargs 不知道函数 f。我猜范围错误。建议解决方案是导出 f (https://stackoverflow.com/a/11003457/7238575),但这似乎没有帮助。其他的 (https://stackoverflow.com/questions/11003418/calling-shell-functions-with-xargs)建议我们还需要调用 bash 的新实例。

但是,如果我尝试

grep -l "stringToBeSearched" * | xargs -n1 bash -c f

它只打印白线列表。

显然,必须有一种更简单的方法来完成像将函数 f 应用于列表这样简单的事情。


输入和输出示例:有一些文件包含文本stringToBeSearched.说一个叫一个名字begin-1.end,一个叫一个名字begin-2.end。假设这些文件隐藏在不包含stringToBeSearched.我想获取那些包含stringToBeSearched.所以在这种情况下,我想获得一个包含 1 和 2 的列表。理想情况下,我还有一种简单的方法将上面未提到的函数应用于f2这些函数。所以最终我希望能够运行f2 1并且 ' f2 2


如果这是一个 XY 问题,我希望得到一个解释为什么这根本不是方法的答案,而不是技术问题的答案。问题的要点不是如何找到我正在寻找的这些数字(尽管我也想得到答案)。就是问将函数应用于列表的一般方法是什么。上面解释的具体问题只是我需要将函数应用于列表的操作的问题的一个实例。它旨在说明无法将函数应用于列表的问题。这并不是主要问题本身。

答案1

要将函数应用于列表,您只需对其进行迭代即可:

list=(one two 'twenty one' banana)

f() {
    echo "This is f applied to '$1'"
}

for item in "${list[@]}"
do
    f "$item"
done

如果您有一个(空格)分隔列表,您可以将其转换为数组(列表)或单步遍历它。请注意,这里未加引号的列表中包含通配符(*, ?, [... ])的任何项目都将在当前目录的上下文中照常进行评估,因此我们需要首先禁用该操作(仅此一点就是使用的一个很好的理由)数组/列表而不是一串空格分隔的项目):

text='one two twenty-one banana'

OIFS="$IFS" IFS=' ' OSHELLOPTS="$SHELLOPTS"
set -o noglob

for item in $text
do
    f "$item"
done

IFS="$OIFS"
[[ ! "$OSHELLOPTS:" =~ [=:]noglob: ]] && set +o noglob

变化比比皆是;这是一个用冒号分隔的列表:

text='one:two:twenty one:banana'

OIFS="$IFS" IFS=':' OSHELLOPTS="$SHELLOPTS"
set -o noglob
...

答案2

所有关于纯函数和柯里化以及其他什么对于具有一流函数的语言来说都是有好处的,但是对于 shell 脚本来说,管道才是您应该寻找的。

当谈到你必须明确地说:从输入中获取行、执行 X、输出时,你需要退后一步并重新检查你正在做的事情。大多数标准工具会自动从输入中获取行,执行 X 和输出,因此通常您只需要获得正确的工具和正确的 X。因此,如果您最终遇到从输入中获取行的情况,请将其用作输入一个已经可以从输入中获取行,然后捕获该命令的输出并将其重新用于输出的工具...有些问题。

在这种情况下,就是sed,X 是's/begin-\([0-9]*\).end/\1/'

另外,旁注:echo $(sed ...)没有意义,直接做即可sed ...。您正在使用命令替换捕获输出,然后......再次将其用作输出。

答案3

看起来您似乎想获取包含 string 的每个此类文件名的N文件名中的整数。begin-N.endstringToBeSearched

您可以通过一个简单的循环来完成此操作:

for filename in begin-*.end; do
    if grep -qF 'stringToBeSearched' "$filename"; then
        N=${filename%.end}
        N=${N#begin-}
        printf '%s\n' "$N"
    fi
done

重点是我们不是迭代文本。包含文件名的文本(这就是 的输出grep -l)在 Unix 系统上编码所有可能的文件名非常糟糕,尤其是包含换行符的文件名。

相反,我们让 glob 模式begin-*.end扩展为正确的清单并迭代它,测试列表中的每个元素,grep然后在找到匹配时提取整数。

如果您愿意,您显然可以将其包装在函数中:

test_files () {
    local func="$1";   shift
    local string="$1"; shift

    # Looks for the string "$string" in all given files.
    # Calls "$func" with each pathname that contains the string.

    for pathname do
        if grep -qF "$string" "$pathname"; then
            "$func" "$pathname"
        fi
    done
}

foo () {
    # Takes a string on the form "begin-N.end" and
    # extracts and prints "N".

    local tmp="${1%.end}"
    printf '%s\n' "${tmp#begin-}"
}

test_files foo stringToBeSearched begin-*.end

这或多或少使用了一种简单的“回调”形式,对于包含特定字符串的每个文件都foo调用该回调。test_files

答案4

如果您有一个列表,通常可以从并行运行命令中受益:

env_parallel --session
f () { echo $(sed 's/begin-\([0-9]*\).end/\1/' <<<$1) ;}
grep -l "stringToBeSearched" * | env_parallel f

或者:

f () { echo $(sed 's/begin-\([0-9]*\).end/\1/' <<<$1) ;}
export -f f
grep -l "stringToBeSearched" * | parallel f

相关内容