简体   繁体   中英

Passing subshell to bash function

I have a set of bash log functions which enable me to comfortably redirect all output to a log file and bail out in case something happens:

#! /usr/bin/env bash

# This script is meant to be sourced

export SCRIPT=$0
if [ -z "${LOG_FILE}" ]; then
    export LOG_FILE="./log.txt"
fi

# https://stackoverflow.com/questions/11904907/redirect-stdout-and-stderr-to-function
# If the message is piped receives info, if the message is a parameter
# receives info, message
log() {
    local TYPE
    local IN
    local PREFIX
    local LINE

    TYPE="$1"
    if [ -n "$2" ]; then
        IN="$2"
    else
        if read -r LINE; then
          IN="${LINE}"
        fi

        while read -r LINE; do
          IN="${IN}\n${LINE}"
        done

        IN=$(echo -e "${IN}")
    fi

    if [ -n "${IN}" ]; then
        PREFIX=$(date +"[%X %d-%m-%y - $(basename "${SCRIPT}")] ${TYPE}: ")
        IN="$(echo "${IN}" | awk -v PREFIX="${PREFIX}" '{printf PREFIX}$0')"
        touch "${LOG_FILE}"
        echo "${IN}" >> "${LOG_FILE}"
    fi
}

# receives message as parameter or piped, logs as info
info() {
    log "( INFO )" "$@" 
}

# receives message as parameter or piped, logs as an error
error() {
    log "(ERROR )" "$@"
}

# logs error and exits
fail() {
    error "$1"
    exit 1
}

# Reroutes stdout to info and sterr to error
log_wrap()
{
    "$@" > >(info) 2> >(error)
    return $?
}

Then I use the functions as follows:

LOG_FILE="logging.log"
source "log_functions.sh"

info "Program started"
log_wrap some_command arg0 arg1 --kwarg=value || fail "Program failed"

Which works. Since log_wrap redirects stdout and sterr I don't want it interfering with commands composed using piping or redirections. Such as:

log_wrap echo "File content" > ~/user_file || fail "user_file could not be created."
log_wrap echo "File content" | sudo tee ~/root_file > /dev/null || fail "root_file could not be created."

So I want a way to group those commands so their redirection is solved and then pass that to log_wrap . I am aware of two ways of grouping:

  • Subshells: They are not meant to be passed around, naturally this:

     log_wrap ( echo "File content" > ~/user_file ) || fail "user_file could not be created."

    throws a syntax error.

  • Braces (grouping?, context?): When called inside a command, the brace is interpreted as an argument.

     log_wrap { echo "File content" > ~/user_file } || fail "user_file could not be created."

    Is roughly equivalent (in my understanding) to:

     log_wrap '{' echo "File content" > ~/user_file '}' || fail "user_file could not be created."

To recapitulate, my question is: Is there a way to pass a composition of commands, in my case composed by redirection/piping, to a bash function?

The way it's set up, you can only pass what Posix calls simple commands -- command names and arguments. No compound commands like subshells or brace groups will work.

However, you can use functions to run arbitrary code in a simple command:

foo() { { echo "File content" > ~/user_file; } || fail "user_file could not be created."; }
log_wrap foo

You could also consider just automatically applying your wrapper to all commands in the rest of the script using exec :

exec > >(info) 2> >(error)
{ echo "File content" > ~/user_file; } || fail "user_file could not be created.";

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM