可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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? That is, so that it's possible to see the commands that were run (as well as their output), similar to the output of make
?
That is, if running a shell script like
echo "Hello, World"
I would like the following output
echo "Hello, World"
Hello, World
Alternatively, is it possible to write a bash function called echo_and_run
that will output a command and then run it?
$ echo_and_run echo "Hello, World"
echo "Hello, World"
Hello, World
回答1:
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
回答2:
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).
回答3:
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
}
回答4:
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.
回答5:
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.
回答6:
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
回答7:
Create executable(+x) base script named as "echo_and_run" with below mentioned simple shell script!
#!/bin/bash
echo "$1"
$1
$ ./echo_and_run "echo Hello, World"
echo Hello, World
Hello, World
However, cnicutar's approch to set -x
is reliable and strongly recommended.