zsh: get filename of the script that called a function
Asked Answered
S

3

5

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"
Sabelle answered 28/2, 2016 at 1:43 Comment(1)
Here is a very similar question but also no real solution.Excavation
S
7

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.

Shoot answered 18/3, 2019 at 2:59 Comment(2)
On zsh 5.8 with Debian: $ZSH_ARGZERO in a function echos zsh when script is called from a terminal and ~/.vscode/extensions/rogalmic.zsh-debug-0.1.3/zshdb_dir/zshdb when run from vscode with zsh-debug extension.Tart
@Tart Was the name of the script which you called from a terminal "zsh"? I'm not sure what the vscode zsh-debug extension does as I don't use vscode, but if it inserts itself into the zsh execution like most debuggers do, that seems like a reasonable output.Shoot
S
3

$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.

Shandy answered 25/2, 2023 at 8:10 Comment(0)
I
1

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
Idelia answered 28/2, 2016 at 2:5 Comment(1)
When sourced on initialization by zsh, $0 is simply zsh in every startup file. It would work if I passed the script name to the function directly, like end_time ${(%):-%x}, but you can see why I'd rather not use that at each invocation :)Sabelle

© 2022 - 2024 — McMap. All rights reserved.