How to use shell functions in a script - zsh

I have a shell function that I am trying to call from my zsh script but it is not finding the function. I assume this is because the script has no context to the environment? How do I get the script to utilize the shell functions?
$ function foo { echo bar }
$ foo
bar
In my my_script.zsh file I have:
#!/usr/bin/zsh
echo Foo
foo
But when I run this I get:
$ ./my_script.zsh
Foo
./my_script.zsh:4: command not found: foo
Is there any way I can use my shell functions inside of my scirpt?

You have not defined the function. While bash would have so-called exported functions, with which you could achieve the desired effect (though I don't consider it wise to do so), zsh doesn't. A common approach is to put the function definitions in a separated file (say, funlib.zsh), and source this file from every zsh process which needs them, i.e. something like
source ~/lib/funlib.zsh
(or wherever you put your function definitions).

Related

bad set of key/value pairs for associative array for custom completion

I am trying to follow Make zsh complete arguments from a file but with a command. Shouldn't matter much.
In /usr/local/share/zsh/site-functions/_foo I have
#compdef foo
aliases=($(a complicated command here printing everything twice))
_describe aliases aliases
The command when ran alone prints this:
foo foo bar bar this this that that
This seems to create an associative array just fine. When I add echo ${(kv)aliases} before the _describe aliases aliases command and run /usr/local/share/zsh/site-functions/_foo I am getting
foo foo bar bar this this that that
And when I just do ${(k)aliases} then
foo bar this that
I do not really a description so this would work for me. But, it doesn't work for zsh.
I added
function foo() { echo $* }
autoload _foo
compdef _foo foo
to ~/.zshrc and after . ~/.zshrc when I type foo [tab] I get _foo:2: bad set of key/value pairs for associative array. I tried changing the command to only print everything once. Same results. I tried changing the command to print "foo:x " for every foo. Same results. So what about should my program produce for this to work?
aliases already exists as an associative array containing all your shell aliases. Call the variable something else.
It may alias help to declare your variable local, for a normal array:
local -a compl_aliases
The bad set of key/value pairs usually indicates that you have an odd number of elements when doing an associative array assignment. There needs to be an even number with a value for each key.

Can I source ksh and expect in the same script?

I am trying to use environment variables set by ksh and the expect command in the same script. However, if I try to source both of them, it doesnt work. Is there a way to source ksh and expect in the same script?
Do something like
#!/usr/bin/ksh
. /path/to/ksh_stuff.sh
export FOO=bar
# other ksh stuff
expect <<'END_EXPECT'
source /path/to/expect_stuff.exp
send_user "FOO is $env(FOO)\n"
# other expect stuff
END_EXPECT
Adding quotes around the here-doc terminator (<<'END_EXPECT') means that the entire here-doc is single quoted, so ksh will not attempt any parameter substitutions on it. This is a effective way to isolate the expect script's variables from ksh.
In Korn shell you dot in the other script, example:
. ${other_script}
This will run in the same process as the parent script. The other script can see any variables that are defined in the parent script. If you want to sub-shell (to run an external command), then you will need to export any variables first.
If you want to reference environment variables in your expect script, (those that are exported by a ksh script that runs expect in a subshell) then your expect script needs to reference a global array env . For example if your ksh script exports MYPATH variable then subshells to expect, the expect might reference $env(MYPATH)

What does autoload do in zsh?

I wasn't able to find a documentation for the widely used autoload command in zsh. Does anybody can explain it in plain English?
A bit more specific: What does autoloading of modules mean, for example in this line:
autoload -Uz vcs_info
What does it do?
I've tried autoload --help, man autoload, googling - no success. Thanks!
The autoload feature is not available in bash, but it is in ksh (korn shell) and zsh. On zsh see man zshbuiltins.
Functions are called in the same way as any other command. There can be a name conflict between a program and a function. What autoload does is to mark that name as being a function rather than an external program. The function has to be in a file on its own, with the filename the same as the function name.
autoload -Uz vcs_info
The -U means mark the function vcs_info for autoloading and suppress alias expansion. The -z means use zsh (rather than ksh) style. See also the functions command.
Edit (from comment, as suggested by #ijoseph):
So it records the fact that the name is a function and not an external program - it does not call it unless the -X option is used, it just affects the search path when it is called. If the function name does not collide with the name of a program then it is not required. Prefix your functions with something like f_ and you will probably never need it.
For more detail see http://zsh.sourceforge.net/Doc/Release/Functions.html.
autoload tells zsh to look for a file in $FPATH/$fpath containing a function definition, instead of a file in $PATH/$path containing an executable script or binary.
Script
A script is just a sequence of commands that get executed when the script is run. For example, suppose you have a file called hello like this:
echo "Setting 'greeting'"
greeting='Hello'
If the file is executable and located in one of the directories in your $PATH, then you can run it as a script by just typing its name. But scripts get their own copy of the shell process, so anything they do can't affect the calling shell environment. The assignment to greeting above will be in effect only within the script; once the script exits, it won't have had any impact on your interactive shell session:
$ hello
Setting 'greeting'
$ echo $greeting
$
Function
A function is instead defined once and stays in the shell's memory; when you call it, it executes inside the current shell, and can therefore have side effects:
hello() {
echo "Setting 'greeting'"
greeting='Hello'
}
$ hello
Setting 'greeting'
$ echo $greeting
Hello
So you use functions when you want to modify your shell environment. The Zsh Line Editor (ZLE) also uses functions - when you bind a key to some action, that action is defined as a shell function (which has to be added to ZLE with the zle -N command).
Now, if you have a lot of functions, then you might not want to define all of them in your .zshrc every time you start a new shell; that slows down shell startup and uses memory to store functions that you might not wind up calling during the lifetime of that shell. So you can instead put the function definitions into their own files, named after the functions they define, and put the files into directories in your $FPATH, which works like $PATH.
Zsh comes with a bunch of standard functions in the default $FPATH already. But it won't know to look for a command there unless you've first told it that the command is a function.
That's what autoload does: it says "Hey, Zsh, this command name here is a function, so when I try to run it, go look for its definition in my FPATH, instead of looking for an executable in my PATH."
The first time you run command which Zsh determines is autoloaded function, the shell sources the definition file. Then, if there's nothing in the file except the function definition, or if the shell option KSH_AUTOLOAD is set, it proceeds to call the function with the arguments you supplied. But if that option is not set and the file contains any code outside the function definition (like initialization of variables used by the function), the function is not called automatically. In that case it's up to you to call the function inside the file after defining it so that first invocation will work.

Make zsh complete arguments from a file

zsh is great but its completion system is very diverse. And the documentation lacks good examples. Is there a template for completing for a specific application. The completion would get its match data from a file, separated by newlines?
I tried modifying an older example of mine that takes match data "live":
~ % cat .zsh/completers/_jazzup
#compdef jazz_up
_arguments "2: :(`mpc lsplaylists|sed -e 's# #\\\\ #g'`)"
I could supply cat my_file there instead of mpc invocation and so on but would there be a more elegant way to do this simple task? And that completion there is placement-specific: can you provide an example where zsh would attempt to complete at any point after the program name is recognized?
The match data will have whitespaces and so on, the completion should escape the WS. Example of that:
Foo bar
Barbaric
Get it (42)
Now if that completion would be configured for a command Say, we should get this kind of behaviour out of zsh:
$ Say Fo<TAB>
$ Say Foo\ bar
$ Say Ge<TAB>
$ Say Get\ it\ \(42\)
Simple completion needs are better addressed with _describe, it pairs an array holding completion options and a description for them (you can use multiple array/description pairs, check the manual).
(_arguments is great but too complex.)
[...]
First create a file
echo "foo\nbar\nbaz\nwith spac e s\noh:noes\noh\:yes" >! ~/simple-complete
Then create a file _simple somewhere in your $fpath:
#compdef simple
# you may wish to modify the expansion options here
# PS: 'f' is the flag making one entry per line
cmds=( ${(uf)"$(< ~/simple-complete)"} )
# main advantage here is that it is easy to understand, see alternative below
_describe 'a description of the completion options' cmds
# this is the equivalent _arguments command... too complex for what it does
## _arguments '*:foo:(${cmds})'
then
function simple() { echo $* }
autoload _simple # do not forget BEFORE the next cmd!
compdef _simple simple # binds the completion function to a command
simple [TAB]
it works. Just make sure the completion file _simple is placed somewhere in your fpath.
Notice that : in the option list is supposed to be used for separating an option from their (individual) description (oh:noes). So that won't work with _describe unless you quote it (oh\:yes). The commented out _arguments example will not use the : as a separator.
Without changing anything further in .zshrc (I already have autoload -Uz compinit
compinit) I added the following as /usr/local/share/zsh/site-functions/_drush
#compdef drush
_arguments "1: :($(/usr/local/bin/aliases-drush.php))"
Where /usr/local/bin/aliases-drush.php just prints a list of strings, each string being a potential first argument for the command drush. You could use ($(< filename)) to complete from filename.
I based this on https://unix.stackexchange.com/a/458850/9452 -- it's surprising how simple this is at the end of the day.

Makefile: conversion from recursive make calls to a sequential flow

I have a Makefile that is executed by "gmake -f Makefile foo" and looks like the following.
foo:
#set var = 1
#$(MAKE) bar var=1
bar:
#hello.mk is included
#echo “success”
ifeq ($(var), 1)
include test\hello.mk
endif
I'd like to convert this to a sequential flow, like something in the following as it has less overhead and does not need to step back in this Makefile.
foo:
$(eval var=1)
#$(bar)
define bar
include test\hello.mk #this doesn’t work
#echo “success”
endef
I cannot have an include statement inside a function or target, so how should I go around this problem to make this Makefile sequential with no make calls?
Short answer: you can't do this.
You want to make 'foo' and 'bar' with what are in effect different makefiles, which you can't do without a recursive call to Make.
If you can put some restrictions on what hello.mk can do, you might be able to get this into one pass.

Resources