首页 > 解决方案 > 通过 GNU 并行将 args 传递给定义的 bash 函数

问题描述

让我向您展示我的 Bash 脚本片段以及我如何尝试并行运行:

    parallel -a "$file" \
            -k \
            -j8 \
            --block 100M \
            --pipepart \
            --bar \
            --will-cite \
            _fix_col_number {} | _unify_null_value {} >> "$OUTPUT_DIR/$new_filename"

因此,我基本上是在尝试使用脚本中定义的 Bash 函数并行处理文件中的每一行。但是,我不确定如何将每一行传递给我定义的函数“_fix_col_number”和“_unify_null_value”。无论我做什么,都不会传递给函数。

我在我的脚本中导出这样的函数:

declare -x NUM_OF_COLUMNS
export -f _fix_col_number
export -f _add_tabs
export -f _unify_null_value

提到的功能是:

_unify_null_value()
{
    _string=$(echo "$1" | perl -0777 -pe "s/(?<=\t)\.(?=\s)//g" | \
              perl -0777 -pe "s/(?<=\t)NA(?=\s)//g" | \
              perl -0777 -pe "s/(?<=\t)No Info(?=\s)//g")
    echo "$_string"
}
_add_tabs()
{
    _tabs=""

    for (( c=1; c<=$1; c++ ))
    do
        _tabs="$_tabs\t"
    done

    echo -e "$_tabs"
}
_fix_col_number()
{
    line_cols=$(echo "$1" | awk -F"\t" '{ print NF }')

    if [[ $line_cols -gt $NUM_OF_COLUMNS ]]; then
        new_line=$(echo "$1" | cut -f1-"$NUM_OF_COLUMNS")
        echo -e "$new_line\n"
    elif [[ $line_cols -lt $NUM_OF_COLUMNS ]]; then
        missing_columns=$(( NUM_OF_COLUMNS - line_cols ))
        new_line="${1//$'\n'/}$(_add_tabs $missing_columns)"
        echo -e "$new_line\n"
    else
        echo -e "$1"
    fi
}

我尝试从并行中删除 {}。不太确定我做错了什么。

标签: bashgnu-parallel

解决方案


我在调用中看到两个问题以及函数的其他问题:

  1. --pipepart没有论据。从中读取的块-a file通过标准输入传递给您的函数。尝试使用以下命令来确认这一点:
    seq 9 > file
    parallel -a file --pipepart echo
    parallel -a file --pipepart cat
    理论上,您可以将 stdin 读入一个变量并将该变量传递给您的函数,
    parallel -a file --pipepart 'b=$(cat); someFunction "$b"'
    ......但我不推荐它,特别是因为您的块是 100MB。
  2. Bash甚至在看到它|之前就解释了你的命令中的管道。parallel要运行管道,请引用整个命令:
    parallel ... 'b=$(cat); _fix_col_number "$b" | _unify_null_value "$b"' >> ...
  3. _fix_col_number似乎假设它的参数是单行,而是接收 100MB 块。
  4. _unify_null_value不读取标准输入,因此_fix_col_number {} | _unify_null_value {}等价于_unify_null_value {}.

话虽如此,您的功能可以大大改善。他们启动了很多进程,这对于较大的文件来说变得非常昂贵。你可以做一些微不足道的改进,比如合并perl ... | perl ... | perl ...成一个perl. 同样,您可以直接处理标准输入,而不是将所有内容存储在变量中:只需f() { cmd1 | cmd2; }使用f() { var=$(echo "$1" | cmd1); var=$(echo "$var" | cmd2); echo "$var"; }.
但是,不要在这些小事上浪费时间。完全重写sedawkperl很容易,并且应该优于现有函数的所有优化。

尝试

n="INSERT NUMBER OF COLUMNS HERE"
tabs=$(perl -e "print \"\t\" x $n")
perl -pe "s/\r?\$/$tabs/; s/\t\K(\.|NA|No Info)(?=\s)//g;" file |
cut -f "1-$n"

如果您仍然觉得这太慢,请忽略file;将命令打包成一个函数,导出该函数,然后调用parallel -a file -k --pipepart nameOfTheFunction. 该选项--block不是必需的,因为pipepart它将根据作业数量平均分配输入(可以使用 指定-j)。


推荐阅读