How to get the complete calling command of a BASH script from inside the script (not just the arguments)
You can try the following:
myInvocation="$(printf %q "$BASH_SOURCE")$((($#)) && printf ' %q' "$@")"
$BASH_SOURCE
refers to the running script (as invoked), and $@
is the array of arguments; (($#)) &&
ensures that the following printf
command is only executed if at least 1 argument was passed; printf %q
is explained below.
While this won't always be a verbatim copy of your command line, it'll be equivalent - the string you get is reusable as a shell command.
chepner points out in a comment that this approach will only capture what the original arguments were ultimately expanded to:
For instance, if the original command was
my_script $USER "$(date +%s)"
,$myInvocation
will not reflect these arguments as-is, but will rather contain what the shell expanded them to; e.g.,my_script jdoe 1460644812
chepner also points that out that getting the actual raw command line as received by the parent process will be (next to) impossible. Do tell me if you know of a way.
- However, if you're prepared to ask users to do extra work when invoking your script or you can get them to invoke your script through an alias you define - which is obviously tricky - there is a solution; see bottom.
Note that use of printf %q
is crucial to preserving the boundaries between arguments - if your original arguments had embedded spaces, something like $0 $*
would result in a different command.printf %q
also protects against other shell metacharacters (e.g., |
) embedded in arguments.
printf %q
quotes the given argument for reuse as a single argument in a shell command, applying the necessary quoting; e.g.:
$ printf %q 'a |b'
a\ \|b
a\ \|b
is equivalent to single-quoted string 'a |b'
from the shell's perspective, but this example shows how the resulting representation is not necessarily the same as the input representation.
Incidentally, ksh
and zsh
also support printf %q
, and ksh
actually outputs 'a |b'
in this case.
If you're prepared to modify how your script is invoked, you can pass $BASH_COMMAND
as an extra argument: $BASH_COMMAND
contains the raw[1]
command line of the currently executing command.
For simplicity of processing inside the script, pass it as the first argument (note that the double quotes are required to preserve the value as a single argument):
my_script "$BASH_COMMAND" --option1 value --option2
Inside your script:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
myInvocation=$1 # Save the command line in a variable...
shift # ... and remove it from "$@".
# Now process "$@", as you normally would.
Unfortunately, there are only two options when it comes to ensuring that your script is invoked this way, and they're both suboptimal:
The end user has to invoke the script this way - which is obviously tricky and fragile (you could however, check in your script whether the first argument contains the script name and error out, if not).
Alternatively, provide an alias that wraps the passing of
$BASH_COMMAND
as follows:alias my_script='/path/to/my_script "$BASH_COMMAND"'
- The tricky part is that this alias must be defined in all end users' shell initialization files to ensure that it's available.
- Also, inside your script, you'd have to do extra work to re-transform the alias-expanded version of the command line into its aliased form:
# The *first* argument is what "$BASH_COMMAND" expanded to,
# i.e., the entire (alias-expanded) command line.
# Here we also re-transform the alias-expanded command line to
# its original aliased form, by replacing everything up to and including
# "$BASH_COMMMAND" with the alias name.
myInvocation=$(sed 's/^.* "\$BASH_COMMAND"/my_script/' <<<"$1")
shift # Remove the first argument from "$@".
# Now process "$@", as you normally would.
Sadly, wrapping the invocation via a script or function is not an option, because the $BASH_COMMAND
truly only ever reports the current command's command line, which in the case of a script or function wrapper would be the line inside that wrapper.
[1] The only thing that gets expanded are aliases, so if you invoked your script via an alias, you'll still see the underlying script in $BASH_COMMAND
, but that's generally desirable, given that aliases are user-specific.
All other arguments and even input/output redirections, including process substitutiions <(...)
are reflected as-is.
Propagate all arguments in a Bash shell script
Use "$@"
instead of plain $@
if you actually wish your parameters to be passed the same.
Observe:
$ cat no_quotes.sh
#!/bin/bash
echo_args.sh $@
$ cat quotes.sh
#!/bin/bash
echo_args.sh "$@"
$ cat echo_args.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4
$ ./no_quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./no_quotes.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:
$ ./quotes.sh first second
Received: first
Received: second
Received:
Received:
$ ./quotes.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:
How can I run a function from a script in command line?
If the script only defines the functions and does nothing else, you can first execute the script within the context of the current shell using the source
or .
command and then simply call the function. See help source
for more information.
Can I call a function of a shell script from another shell script?
Refactor your second.sh
script like this:
func1 {
fun="$1"
book="$2"
printf "func=%s,book=%s\n" "$fun" "$book"
}
func2 {
fun2="$1"
book2="$2"
printf "func2=%s,book2=%s\n" "$fun2" "$book2"
}
And then call these functions from script first.sh
like this:
source ./second.sh
func1 love horror
func2 ball mystery
OUTPUT:
func=love,book=horror
func2=ball,book2=mystery
How to echo shell commands as they are executed
set -x
or set -o xtrace
expands variables and prints a little + sign before the line.
set -v
or set -o verbose
does not expand the variables before printing.
Use set +x
and set +v
to turn off the above settings.
On the first line of the script, one can put #!/bin/sh -x
(or -v
) to have the same effect as set -x
(or -v
) later in the script.
The above also works with /bin/sh
.
See the bash-hackers' wiki on set
attributes, and on debugging.
$ cat shl
#!/bin/bash
DIR=/tmp/so
ls $DIR
$ bash -x shl
+ DIR=/tmp/so
+ ls /tmp/so
$
How to access command line arguments of the caller inside a function?
My reading of the Bash Reference Manual says this stuff is captured in BASH_ARGV,
although it talks about "the stack" a lot.
#!/bin/bash
shopt -s extdebug
function argv {
for a in ${BASH_ARGV[*]} ; do
echo -n "$a "
done
echo
}
function f {
echo f $1 $2 $3
echo -n f ; argv
}
function g {
echo g $1 $2 $3
echo -n g; argv
f
}
f boo bar baz
g goo gar gaz
Save in f.sh
$ ./f.sh arg0 arg1 arg2
f boo bar baz
fbaz bar boo arg2 arg1 arg0
g goo gar gaz
ggaz gar goo arg2 arg1 arg0
f
fgaz gar goo arg2 arg1 arg0
Reliable way for a Bash script to get the full path to itself
Here's what I've come up with (edit: plus some tweaks provided by sfstewman, levigroker, Kyle Strand, and Rob Kennedy), that seems to mostly fit my "better" criteria:
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
That SCRIPTPATH
line seems particularly roundabout, but we need it rather than SCRIPTPATH=`pwd`
in order to properly handle spaces and symlinks.
The inclusion of output redirection (>/dev/null 2>&1
) handles the rare(?) case where cd
might produce output that would interfere with the surrounding $( ... )
capture. (Such as cd
being overridden to also ls
a directory after switching to it.)
Note also that esoteric situations, such as executing a script that isn't coming from a file in an accessible file system at all (which is perfectly possible), is not catered to there (or in any of the other answers I've seen).
The --
after cd
and before "$0"
are in case the directory starts with a -
.
Related Topics
Can't Register a Snapshot Repository in Elasticsearch
How to Idiomatically Package Dependencies for a Qt Application Using Cpack
Self Updating Bash Script from Github
Hidden File .Env Not Copied Using Docker Copy
How to Get The System Home Directory in Cmake on Linux
How to Get Yesterday and Day Before Yesterday in Linux
Loop Through Array of Arrays of String with Spaces
How to Get My Cuda Specs on a Linux Machine
Recommended Irc Server (Ircd) for a Small Site
Linux: How to Enable Execute in Place (Xip) for Ramfs/Tmpfs
Can 'Find' or Any Other Tool Search for Files Breadth-First
Symbol Lookup Error: ./Executablename: Undefined Symbol: _Zn18Qxmldefaulthandlerc2Ev
Compiling a Driver as a Part of a Kernel, Not as a Module
How to Parse Http Headers Using Bash
What Do I Need to Debug Pthreads
Can't Access Publicly Exposed Docker Container Port from External Machine, Only from Localhost
How to Remember Multiple Tabs' Session in Terminal? (Alike Ff Session Manager)