Can I make a shell function in as a pipeline conditionally “disappear”, without using cat?

只谈情不闲聊 提交于 2020-08-08 18:19:14

问题


I have a bash script that produces some text from a pipe of commands. Based on a command line option I want to do some validation on the output. For a contrived example...

CHECK_OUTPUT=$1
...
check_output()
{
    if [[ "$CHECK_OUTPUT" != "--check" ]]; then
        # Don't check the output. Passthrough and return.
        cat
        return 0
    fi

    # Check each line exists in the fs root
    while read line; do
        if [[ ! -e "/$line" ]]; then
            echo "Error: /$line does not exist"
            return 1
        fi
        echo "$line"
    done
    return 0
}

ls /usr | grep '^b' | check_output

[EDIT] better example: https://stackoverflow.com/a/52539364/1888983

This is really useful, particularly if I have multiple functions that can becomes passthroughs. Yes, I could move the CHECK_OUTPUT conditional and create a pipe with or without check_output but I'd need to write lines for each combination for more functions. If there are better ways to dynamically build a pipe I'd like to know.

The problem is the "useless use of cat". Can this be avoided and make check_output like it wasn't in the pipe at all?


回答1:


Yes, you can do this -- by making your function a wrapper that conditionally injects a pipeline element, instead of being an unconditional pipeline element itself. For example:

maybe_checked() {
  if [[ $CHECK_OUTPUT != "--check" ]]; then
    "$@" # just run our arguments as a command, as if we weren't here
  else
    # run our arguments in a process substitution, reading from stdout of same.
    # ...some changes from the original code:
    #   IFS= stops leading or trailing whitespace from being stripped
    #   read -r prevents backslashes from being processed
    local line # avoid modifying $line outside our function
    while IFS= read -r line; do
      [[ -e "/$line" ]] || { echo "Error: /$line does not exist" >&2; return 1; }
      printf '%s\n' "$line"  # see https://unix.stackexchange.com/questions/65803
    done < <("$@")
  fi
}

ls /usr | maybe_checked grep '^b'

Caveat of the above code: if the pipefail option is set, you'll want to check the exit status of the process substitution to have complete parity with the behavior that would otherwise be the case. In bash version 4.3 or later (IIRC), $? is modified by process substitutions to have the relevant PID, which can be waited for to retrieve exit status.

That said, this is also a use case wherein using cat is acceptable, and I'm saying this as a card-carying member of the UUOC crowd. :)


Adopting the examples from John Kugelman's answers on the linked question:

maybe_sort() {
    if (( sort )); then
        "$@" | sort
    else
        "$@"
    fi
}

maybe_limit() {
    if [[ -n $limit ]]; then
        "$@" | head -n "$limit"
    else
        "$@"
    fi
}

printf '%s\n' "${haikus[@]}" | maybe_limit maybe_sort sed -e 's/^[ \t]*//'


来源:https://stackoverflow.com/questions/63023975/can-i-make-a-shell-function-in-as-a-pipeline-conditionally-disappear-without

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!