Echo command, and then run it? (Like make)

前端 未结 6 1495
走了就别回头了
走了就别回头了 2021-02-02 12:49

Is there some way to get bash into a sort of verbose mode where, such that, when it\'s running a shell script, it echoes out the command it\'s going to run before running it? T

相关标签:
6条回答
  • 2021-02-02 13:33

    It's possible to use bash's printf in conjunction with the %q format specifier to escape the arguments so that spaces are preserved:

    function echo_and_run {
      echo "$" "$@"
      eval $(printf '%q ' "$@") < /dev/tty
    }
    
    0 讨论(0)
  • 2021-02-02 13:34

    To add to others' implementations, this is my basic script boilerplate, including argument parsing (which is important if you're toggling verbosity levels).

    #!/bin/sh
    
    # Control verbosity
    VERBOSE=0
    
    # For use in usage() and in log messages
    SCRIPT_NAME="$(basename $0)"
    
    ARGS=()
    
    # Usage function: tells the user what's up, then exits.  ALWAYS implement this.
    # Optionally, prints an error message
    # usage [{errorLevel} {message...}
    function usage() {
        local RET=0
        if [ $# -gt 0 ]; then
            RET=$1; shift;
        fi
        if [ $# -gt 0 ]; then
            log "[$SCRIPT_NAME] ${@}"
        fi
        log "Describe this script"
        log "Usage: $SCRIPT_NAME [-v|-q]" # List further options here
        log "   -v|--verbose    Be more verbose"
        log "   -q|--quiet      Be less verbose"
        exit $RET
    }
    
    # Write a message to stderr
    # log {message...}
    function log() {
        echo "${@}" >&2
    }
    
    # Write an informative message with decoration
    # info {message...}
    function info() {
        if [ $VERBOSE -gt 0 ]; then
            log "[$SCRIPT_NAME] ${@}"
        fi
    }
    
    # Write an warning message with decoration
    # warn {message...}
    function warn() {
        if [ $VERBOSE -gt 0 ]; then
            log "[$SCRIPT_NAME] Warning: ${@}"
        fi
    }
    
    # Write an error and exit
    # error {errorLevel} {message...}
    function error() {
        local LEVEL=$1; shift
        if [ $VERBOSE -gt -1 ]; then
            log "[$SCRIPT_NAME] Error: ${@}"
        fi
        exit $LEVEL
    }
    
    # Write out a command and run it
    # vexec {minVerbosity} {prefixMessage} {command...}
    function vexec() {
        local LEVEL=$1; shift
        local MSG="$1"; shift
        if [ $VERBOSE -ge $LEVEL ]; then
            echo -n "$MSG: "
            local CMD=( )
            for i in "${@}"; do
                # Replace argument's spaces with ''; if different, quote the string
                if [ "$i" != "${i/ /}" ]; then
                    CMD=( ${CMD[@]} "'${i}'" )
                else
                    CMD=( ${CMD[@]} $i )
                fi
            done
            echo "${CMD[@]}"
        fi
        ${@}
    }
    
    # Loop over arguments; we'll be shifting the list as we go,
    # so we keep going until $1 is empty
    while [ -n "$1" ]; do
        # Capture and shift the argument.
        ARG="$1"
        shift
        case "$ARG" in
            # User requested help; sometimes they do this at the end of a command
            # while they're building it.  By capturing and exiting, we avoid doing
            # work before it's intended.
            -h|-\?|-help|--help)
                usage 0
                ;;
            # Make the script more verbose
            -v|--verbose)
                VERBOSE=$((VERBOSE + 1))
                ;;
            # Make the script quieter
            -q|--quiet)
                VERBOSE=$((VERBOSE - 1))
                ;;
            # All arguments that follow are non-flags
            # This should be in all of your scripts, to more easily support filenames
            # that start with hyphens.  Break will bail from the `for` loop above.
            --)
                break
                ;;
            # Something that looks like a flag, but is not; report an error and die
            -?*)
                usage 1 "Unknown option: '$ARG'" >&2
                ;;
            #
            # All other arguments are added to the ARGS array.
            *)
                ARGS=(${ARGS[@]} "$ARG")
                ;;
        esac
    done
    # If the above script found a '--' argument, there will still be items in $*;
    # move them into ARGS
    while [ -n "$1" ]; do
        ARGS=(${ARGS[@]} "$1")
        shift
    done
    
    # Main script goes here.
    

    Later...

    vexec 1 "Building myapp.c" \
        gcc -c myapp.c -o build/myapp.o ${CFLAGS}
    

    Note: This will not cover piped commands; you need to bash -c those sorts of things, or break them up into intermediate variables or files.

    0 讨论(0)
  • 2021-02-02 13:40

    You could make your own function to echo commands before calling eval.

    Bash also has a debugging feature. Once you set -x bash will display each command before executing it.

    cnicutar@shell:~/dir$ set -x
    cnicutar@shell:~/dir$ ls
    + ls --color=auto
    a  b  c  d  e  f
    
    0 讨论(0)
  • 2021-02-02 13:41

    Two useful shell options that can be added to the bash command line or via the set command in a script or interactive session:

    • -v Print shell input lines as they are read.
    • -x After expanding each simple command, for command, case command, select command, or arithmetic for command, display the expanded value of PS4, followed by the command and its expanded arguments or associated word list.
    0 讨论(0)
  • 2021-02-02 13:46

    For extra timestamps and I/O info, consider the annotate-output command from Debian's devscripts package:

    annotate-output echo hello
    

    Output:

    13:19:08 I: Started echo hello
    13:19:08 O: hello
    13:19:08 I: Finished with exitcode 0
    

    Now look for a file that doesn't exist, and note the E: for STDERR output:

    annotate-output ls nosuchfile
    

    Output:

    13:19:48 I: Started ls nosuchfile
    13:19:48 E: ls: cannot access 'nosuchfile': No such file or directory
    13:19:48 I: Finished with exitcode 2
    
    0 讨论(0)
  • 2021-02-02 13:50

    To answer the second part of your question, here's a shell function that does what you want:

    echo_and_run() { echo "$*" ; "$@" ; }
    

    I use something similar to this:

    echo_and_run() { echo "\$ $*" ; "$@" ; }
    

    which prints $ in front of the command (it looks like a shell prompt and makes it clearer that it's a command). I sometimes use this in scripts when I want to show some (but not all) of the commands it's executing.

    As others have mentioned, it does lose quotation marks:

    $ echo_and_run echo "Hello, world"
    $ echo Hello, world
    Hello, world
    $ 
    

    but I don't think there's any good way to avoid that; the shell strips quotation marks before echo_and_run gets a chance to see them. You could write a script that would check for arguments containing spaces and other shell metacharacters and add quotation marks as needed (which still wouldn't necessarily match the quotation marks you actually typed).

    0 讨论(0)
提交回复
热议问题