条件进程替换

条件进程替换

我有一个带有多个参数的命令:

my_command --arg1 <arg1> \
    --arg2 <arg2> \
    --arg3 <arg3>

我将 的调用包装my_command在一个函数中,该函数设置各种参数的值,这些参数可能会被传递,也可能不会被传递:

run_my_command() {
    if [[ ... ]]; then
        ARG1FLAGS="--arg1 $ARG1"
    else
        ARG1FLAGS=''
    fi
    if [[ ... ]]; then
        ARG2FLAGS="--arg2 $ARG2"
    else
        ARG2FLAGS=''
    fi
    if [[ ... ]]; then
        ARG3FLAGS="--arg3 $ARG3"
    else
        ARG3FLAGS=''
    fi

    my_command $ARG1FLAGS \
        $ARG2FLAGS \
        $ARG3FLAGS
}

在某些情况下,有必要将文件名参数传递给my_command

run_my_command() {
    # ...
    if [[ ... ]]; then
        FILEARG="--input_file $SOME_FILE"
    else
        FILEARG=''
    fi

    my_command $FILEARG \
        ...
}

所有这些都运行良好。现在,我想有条件地使用进程替换FILEARG,但这当然行不通:

run_my_command() {
    # ...
    if [[ ... ]]; then
        FILEFLAG='--input_file'
        FILEARG=<(other_cmd)
    else
        FILEARG=''
    fi

    my_command $FILEFLAG $FILEARG \
        ...
}

因为到运行时my_command$FILEARG,一个看起来像 的匿名管道/dev/fd/63已经关闭。

现在我已经通过将所有内容my_command放入条件来解决这个问题:

run_my_command() {
    # Get ARG1, ARG2, ARG3...
    if [[ ... ]]; then
        my_command --input_file <(other_cmd) \
            $ARG1FLAGS \
            $ARG2FLAGS \
            $ARG3FLAGS
    else
        my_command $ARG1FLAGS \
            $ARG2FLAGS \
            $ARG3FLAGS
    fi
}

但我不喜欢重复。我觉得也许我可以用 做一些事情eval,或者我可以将其放入my_command另一个函数中,但我还没有弄清楚。如何有条件地使用进程替换来生成一个my_command可以读取的文件,而无需复制整个调用my_command

请注意,我运行的是 bash 4.4.19。令我相当惊讶的是,bash 3.2.57 的行为似乎确实符合下面的 Bash Hackers wiki 建议的方式:

doit() {
    local -r FOO=<(echo hi)
    cat $FOO
}

doit

# bash 3.2.57:
$ ./test.sh
hi
# bash 4.4.19:
$ ./test.sh
cat: /dev/fd/63: Bad file descriptor

以下是我检查过的一些问题,但我无法从中得到有效的答案:

另外,Bash 黑客维基注释相当神秘的是:

如果进程替换被扩展为函数的参数,在函数调用期间扩展为环境变量,或扩展为函数内的任何赋值,则进程替换将“保持打开”状态,以供函数内的任何命令使用或其被调用者,直到设置它的函数返回。如果在被调用者中再次设置相同的变量,除非新变量是本地变量,否则先前的进程替换将关闭,并且当被调用者返回时,调用者将无法使用该变量。

本质上,扩展到函数内变量的进程替换保持打开状态,直到发生进程替换的函数返回 - 即使分配给由函数调用者设置的局部变量也是如此。动态作用域并不能保护它们不被关闭。

但我无法仅通过将进程替换分配给函数中的变量来“保持打开状态”。

答案1

这更像是回避问题,但是如果将其他参数放在数组中而不是单个变量中,则重复会更少。$ARGnFLAGS

在任何情况下,这也是正确的做法,因为您可以获得命令的参数,而不会妨碍分词或文件名通配。

run_my_command() {
    args=()
    if [[ ... ]]; then
        args+=(--arg1 "$ARG1")
    fi
    if [[ ... ]]; then
        args+=(--arg2 "$ARG2")
    fi

    if [[ ... ]]; then
        my_command --input_file <(other_cmd) "${args[@]}"
    else
        my_command "${args[@]}" 
    fi
}

答案2

run_my_command() {
    # ...
    if [[ ... ]]; then
        tmpfile=$(mktemp)
        trap 'rm -f "$tmpfile"' RETURN
        other_cmd >"$tmpfile"
        FILEFLAG='--input_file'
        FILEARG="$tmpfile"
    else
        FILEARG=''
    fi

    my_command $FILEFLAG $FILEARG \
        ...

    return
}

...或类似的东西。

也就是说,使用临时文件而不是尝试使用进程替换。

答案3

您可以使用以下方法eval来获得您想要的东西:

run_my_command() {
    # Get ARG1, ARG2, ARG3...
    commandline="my_command $ARG1FLAGS $ARG2FLAGS $ARG3FLAGS"
    if [[ ... ]]; then
        commandline="$commandline --input_file <(other_cmd)"
    fi
    eval $commandline
}

相关内容