Reputation: 3569
Inside a function, how can I get the file name of whatever script called that function?
Via prompt expansion, %x
provides the name of the file that defines the function. %N
provides the function name. How do I get the name of the file that called the function?
This would be useful to report the runtime of each of my startup scripts, e.g.:
~/.zshenv:
function start_timer() {
start_time=$(date +%s.%N)
}
function stop_timer() {
stop_time=$(date +%s.%N)
elapsed_time=$((stop_time-start_time))
echo "$SCRIPTNAME took $elapsed_time seconds"
}
start_timer
# initialize env...
end_timer # "~/.zshenv took 0 seconds"
~/.zshrc:
start_timer
# initialize interactive shell...
end_timer # "~/.zshrc took 2 seconds"
~/.zlogin:
start_timer
# initialize login shell...
end_timer # "~/.zlogin took 2 seconds"
Upvotes: 5
Views: 4284
Reputation: 43
$0
doesn't work for sourced scripts. Try one of these options, which both work for me when added to my .zshrc*:
Option 1
Use ${(%):-%N}
or ${(%):-%x}
to get the path to the sourced script (either of these expansions worked for me).
sourcedFile=${(%):-%1N};
echo "${sourcedFile} took ${sec} second(s) to load.";
output:
.zshrc took 2 second(s) to load.
Option 2)
Makes use lsof +p $$
to get the name of the sourced script right when it is being sourced by zsh
. Then, extract the line you need with something like grep
and assign it to your variable. You can then use zsh expansion to extract the last "component" of the line:
sourcedFile=$(lsof +p $$ | grep zshrc);
echo "${sourcedFile:t0} took ${sec} second(s) to load.";
output:
.zshrc took 2 second(s) to load.
Option 2 obviously uses more cycles due to its use of lsof
The expansion examples above are based on the zsh documentation. See man zshexpn
> HISTORY EXPANSION > Modifiers and man zshmisc
> SIMPLE PROMPT ESCAPES > Shell state for more detailed explanations.
Regarding Option 1, from man zshmisc
:
%N The name of the script, sourced file, or shell function that zsh is currently executing, whichever was started most recently. If there is none, this is equivalent to the parameter $0. An integer may follow the `%' to specify a number of trailing path components to show; zero means the full path. A negative integer specifies leading components.
For Option 1 above, please see this post as my source for this solution.
* Note: I'm currently on zsh 5.8.1 (x86_64-apple-darwin22.0) i.e. macOS 13.2.1, where I tested both options successfully.
Upvotes: 3
Reputation: 4029
At least in zsh 5.7.1, the ZSH_ARGZERO
variable will report the name of the current script, even if the function within which it is called is different:
$ cat foo
function a_function_in_foo {
echo "ZSH_ARGZERO=$ZSH_ARGZERO"
}
$ cat bar
source foo
a_function_in_foo
$ zsh bar
ZSH_ARGZERO=bar
It is documented at http://zsh.sourceforge.net/Doc/Release/Parameters.html as
ZSH_ARGZERO
If zsh was invoked to run a script, this is the name of the script. Otherwise, it is the name used to invoke the current shell. This is the same as the value of $0 when the POSIX_ARGZERO option is set, but is always available.
Upvotes: 7
Reputation: 181
You could pass the name as a parameter.
~/.zshenv:
function start_timer() {
echo "File: `basename $@`" # <----
start_time=$(date +%s.%N)
}
~/.zshrc:
start_timer $0 # <----
# initialize interactive shell...
end_timer # "~/.zshrc took 2 seconds"
basename only strips everything before the last slash in case you only want to show the file name.
My test:
File: aliases.zsh
took 0.0018649101257324219 seconds
And - if you call scripts by yourself you also can use time
as a prefix command to print their execution times.
$ time touch x
touch x 0.00s user 0.00s system 7% cpu 0.019 total
Upvotes: 1