0

I write a lot of little useful functions, and sometimes I like to share them with friends or the world. The problem is that I use a lot of my helper functions and aliases in my functions, and I don't feel motivated to read the function code line by line and find all of the dependencies. I am pretty sure this process can be automated; We first need to tokenize the code of the function we want to export (using which func to get its code), then export any aliases of these tokens, and repeat this process recursively for each token (do a which token and see if it exists and is shell-related (so we do nothing for binaries, or perhaps optionally add all binaries to a binaries.txt), and repeat).

Are there any existing tools for this use case?

If not, how do I tokenize the code?

Update: I know my suggested method exports extraneous things and doesn't guarantee that it has exported all the dependencies, but it works for my code.

Update: Since my functions tend to be simple, it is also okay if the exporting tool just runs the function with my sample arguments and exports all dependencies that were needed for this particular run.

  • 1
    Now, how about a function defined as f1() { eval 'f2 foo'; }, or f1() { "$1" foo; }; f1 f2. How are you going to find out f1 actually depends on f2? – Stéphane Chazelas Dec 11 '18 at 21:54
  • @StéphaneChazelas I planned to take tokens out of strings, too, since I do use strings for eval a lot. The second example is not causing a problem. If f1 f2 is in the code, then f2 will be exported, if not, we don't need to support it. – HappyFace Dec 11 '18 at 21:59
  • @StéphaneChazelas I know this approach exports a lot of unnecessary stuff, but that's a tradeoff I am willing to accept. – HappyFace Dec 11 '18 at 22:01
  • @StéphaneChazelas I just noticed that indeed this approach can be fooled by say using a function like evil() { eval "$1""$2"}; evil long func, but I don't do these kinds of stuff. This is a very practical question. :D – HappyFace Dec 11 '18 at 22:28
  • You can't just do string matching. A function could mention f2 in a string without this being related to a previously defined function f2. What you are looking at doing is implementing a full shell parser. – Kusalananda Dec 12 '18 at 9:54
1

The way shells interpret their code, that can only be approximated with some crude heuristics. For instance as an extreme case, there's no way to know that in:

foo() { "${0+b}$(echo AR | tr '[:upper:]' '[:lower:]')$1"; }
bar() { for i in 1 2; do foo "$i" "$i"; }

the bar function depends on the foo, bar1 and bar2 functions without running it (and you'd need to force the running to go through all code paths).

zsh does have a shell tokenising operator: the z parameter expansion flag (also Q to remove one level of quoting). But that's not what you want here.

For instance, in f() { foo "bar baz" "bar $(bar)"; }, ${(z)functions[f]} would return foo, "bar baz" and "bar $(bar)", you would miss the call to the bar function.

One approach could be to extract words that do look like typical function names (in zsh, function names can contain anything, but people rarely use anything but [[:alnum:]:._-] characters in them) and check them against the list of function name.

all_functions=(${(k)functions})
list-functions() (
  typeset -aU processed unprocessed=("$@") new list
  while (($#unprocessed)) {
    for f ($unprocessed) {
      autoload +X $f 2> /dev/null
      list=(${(s.:.)functions[$f]//[^[:alnum:]:._-]/:})
      new+=(${all_functions:*list})
      processed+=($f)
    }
    unprocessed=(${new:|processed})
  }
  print -rol -- $processed
)

Tested on the _path_commands function from the completion system, I get:

_all_labels
_alternative
_arguments
c
_cache_invalid
_call_program
_call_whatis
_command_names
_complete
_completers
_dates
_delimiters
_describe
_description
_dispatch
_files
_globflags
_globqual_delims
_globquals
_groups
_have_glob_qual
_history_modifiers
_ignored
_jobs
_list_files
_main_complete
_message
_next_label
_next_tags
_nm
_normal
_object_files
_parameters
_path_commands
_path_commands_caching_policy
_path_files
_pick_variant
_requested
_retrieve_cache
_services
_set_command
_setup
_store_cache
_suffix_alias_files
_tags
u
_users
_wanted

(run functions $(list-functions _path_commands) to dump the definition of all those functions).

You can spot at least two false positives in there: c and u, two functions in my ~/.zshrc. c and u were probably used as variable names or parameter expansion flags or words in some command arguments, little we can do about that.

I wouldn't worry too much about aliases. Aliases are not expanded at run time in functions. They are expanded at function definition or loading time, but then the output of functions would show the expanded form.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.