How to complete a variable number of arguments containing spaces - zsh

I've build a command line tool and I need to complete arguments with zsh. I never wrote a zsh completion function so I looked in the scripts provided with zsh but I missed something so that it could work properly.
So, mytool can take a variable number of values and two options.
Here are some call examples:
mytool ONE
mytool ONE TWO
mytool AAA BBB CCC DDD EEE --info
In order to complete the values, I hava another executable that outputs all possible lines to stdout, like this simplified script named getdata:
#!/usr/local/bin/zsh
echo ONE
echo TWO ONE
echo TWO TWO
# ... a lot of lines
echo OTHER ONE
echo ONE ANOTHER LINE
echo AAA BBB CCC DDD EEE
Each completion must match to a whole line, so in my getdata example, it will not be possible to just complete with the value TWO because this whole line does not exist, it must be TWO ONE or TWO TWO.
As this script is quite time consuming, I would like to use zsh caching feature. So, here is my zsh complete script:
compdef _complete_mytool mytool
__mytool_caching_policy() {
oldp=( "$1"(Nmh+1) ) # 1 hour
(( $#oldp ))
}
__mytool_deployments() {
local cache_policy
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
if [[ -z "$cache_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __mytool_caching_policy
fi
if ( [[ ${+_mytool_values} -eq 0 ]] || _cache_invalid mytool_deployments ) \
&& ! _retrieve_cache mytool_deployments;
then
local -a lines
_mytool_values=(${(f)"$(_call_program values getdata)"})
_store_cache mytool_deployments _mytool_values
fi
_describe "mytool values" _mytool_values
}
_complete_mytool() {
integer ret=1
local -a context expl line state state_descr args
typeset -A opt_args
args+=(
'*:values:->values'
'--help[show this help message and exit]'
'(-i --info)'{-i,--info}'[display info about values and exit]'
'(-v --version)'{-v,--version}'[display version about values and exit]'
)
_call_function res __mytool_deployments
return ret
}
But when I try to complete, spaces are escaped with backslash, and I don't want this behaviour.
mytool OTHER\ ONE
The options seem not to be completed too... So, any help will be greatly appreciated.

Thanks to okdana on the freenode zsh channel who helped me a lot.
So, the solution is:
compdef _complete_mytool mytool
__mytool_caching_policy() {
oldp=( "$1"(Nmh+1) ) # 1 hour
(( $#oldp ))
}
__mytool_deployments() {
local cache_policy
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
if [[ -z "$cache_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __mytool_caching_policy
fi
if ( [[ ${+_mytool_values} -eq 0 ]] || _cache_invalid mytool_deployments ) \
&& ! _retrieve_cache mytool_deployments;
then
local -a lines
_mytool_values=(${(f)"$(_call_program values getdata)"})
_store_cache mytool_deployments _mytool_values
fi
_describe "mytool values" _mytool_values -Q
}
_complete_mytool() {
_arguments : \
': :__mytool_deployments' \
'--help[show this help message and exit]' \
'(-i --info)'{-i,--info}'[display info about values and exit]' \
'(-v --version)'{-v,--version}'[display version about values and exit]'
}

Related

In zsh, is the tag "dynamic-dirs" special?

I’m wanting to do basically what this function does. It comes from the zsh manual page and other zsh documentation. I’m trying to “vaguely understand” without diving into the fine details what this function is doing and how it works.
In this case, I understand everything more or less except the _wanted line and in particular, is the tag "dynamic-dirs" any arbitrary tag or does it need to match what the higher level driving functions are looking for? I read briefly about tags and it seems like they would need to match up but I can’t find dynamic-dirs anywhere in any code that I’ve grep’ed. Nor have a found any type of list of tags that are used and what they means except the example of “files” and “directories” mentioned in the first few paragraphs of zshcompsys(1).
zsh_directory_name() {
emulate -L zsh
setopt extendedglob
local -a match mbegin mend
if [[ $1 = d ]]; then
# turn the directory into a name
if [[ $2 = (#b)(/home/pws/perforce/)([^/]##)* ]]; then
typeset -ga reply
reply=(p:$match[2] $(( ${#match[1]} + ${#match[2]} )) )
else
return 1
fi
elif [[ $1 = n ]]; then
# turn the name into a directory
[[ $2 != (#b)p:(?*) ]] && return 1
typeset -ga reply
reply=(/home/pws/perforce/$match[1])
elif [[ $1 = c ]]; then
# complete names
local expl
local -a dirs
dirs=(/home/pws/perforce/*(/:t))
dirs=(p:${^dirs})
_wanted dynamic-dirs expl 'dynamic directory' compadd -S\] -a dirs
return
else
return 1
fi
return 0
}
The other question is about the ’n’ section. It is going to return a reply even if the directory doesn’t exist. Am I reading the code right?
I guess this would be nice if I was going to do mkdir ~p:foodog ?

Unix partial string comparison

I need to compare a string partially to check for a given condition.
Like my $1 will be checked if it has a part of a string BLR
while my file input has $1 entries as BLR21 BLR64 IND23
I only need a true condition when $1 is equal to BLR**
where these stars can be anything.
I used a simple if condition
if($1=="BLR21")
{print $2}
Now this only works when whole BLR21 is there in row.
I need to ckeck not for BLR21 but only BLR.
Please Help
Your question is not great, I hope I understood.
Quick and easy solution
grep BLR input.txt
This will output all the lines in which "BLR" is found, in file input.txt. It will match "BLR" with any prefix and suffix, whatever they might be (spaces, alphanumerical, tabs, ...).
"Complicated" solution
A bit more complicated. It does the same thing, but makes sure input.txt exists, and is in the form of a script.
Input file, input.txt:
BLR21 BLR64 IND23
Your script could be:
#!/bin/bash
#
# Arguments
inputfile="input.txt"
if [[ $# -ne 1 ]]
then
echo "Usage: myscript.bash <STRING>"
exit 1
else
string="$1"
fi
# Validation, and processing...
if [[ ! -f "$inputfile" ]]
then
echo "ERROR: file >>$inputfile<< does not exist."
exit 2
else
grep "$string" "$inputfile"
fi
And to call the script, you do:
./myscript.bash BLR
But really, a simple grep does the job here.
Taking it even further...
#!/bin/bash
#
# Arguments
inputfile="input.txt"
if [[ $# -ne 1 ]]
then
echo "Usage: check.bash <STRING>"
exit 1
else
string="$1"
fi
# Validation, and processing...
if [[ ! -f "$inputfile" ]]
then
echo "ERROR: file >>$inputfile<< does not exist."
exit 2
else
while read -r line
do
if [[ "$line" =~ $string ]]
then
echo "$line"
fi
done <"$inputfile"
fi
Now this one is like going to the moon via mars...
It reads each line of the file, one by one. Then it checks if that line contains the string, using the =~ operator inside the if.
But this is crazy, when a simple grep would do.

Not able to change global variable in function used for zsh prompt

I'm trying to build a zsh function that returns an output based on a time interval. Initially the "You're thirsty" condition is true, but after changing the variable thirsty through the command line and setting it to false, the initial if statement goes through, but the variable thirsty in it doesn't change the global variable thirsty. Is there a way to modify the global variable thirsty?
thirsty=
last_time=
drink_water() {
echo -n "$thirsty"
if [[ $thirsty == false ]]; then
last_time="$[$(date +%s) + 10]"
thirsty=true
echo -n "${last_time} $(date +%s) ${thirsty}"
elif [[ $[last_time] -lt $(date +%s) ]]; then
echo -n "💧 You're thirsty"
fi
}
Since your code is actually called from:
PROMPT='$(drink_water)'
...everything it contains is run in a subprocess spawned as part of this command substitution operation ($() is a "command substitution": It creates a new subprocess, runs the code given in that subprocess, and reads the subprocess's output). When that subprocess exits, changes to variables -- even global variables -- made within the subprocess are lost.
If you put your update code directly inside a precmd function, then it would be run before each prompt is printed but without a command substitution intervening. That is:
precmd() {
local curr_time=$(date +%s) # this is slow, don't repeat it!
if [[ $thirsty = false ]]; then
last_time="$(( curr_time + 10 ))"
thirsty=true
PROMPT="$last_time $curr_time $thirsty"
elif (( last_time < curr_time )); then
PROMPT="💧 You're thirsty"
fi
}
Of course, you can set your PROMPT with a command substitution, but updates to variable state have to be done separately, outside that command substitution, if they are to persist.
You can use Zsh Hooks.
Hooks avoid the issues of command substitution here because they run in the same shell, rather than a subshell.
drink_water_prompt=
thirsty=
last_time=
drink_water_gen_prompt() {
drink_water_prompt="$thirsty"
if [[ $thirsty == false ]]; then
last_time="$[$(date +%s) + 10]"
thirsty=true
drink_water_prompt+="${last_time} $(date +%s) ${thirsty}"
elif [[ $[last_time] -lt $(date +%s) ]]; then
drink_water_prompt+="💧 You're thirsty"
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook precmd drink_water_gen_prompt
PROMPT='${drink_water_prompt}'
These also allow more than one precmd() function.

Running custom zsh function for tmux status bar not displaying output

I'm wrote a function called test_status that I am trying to incorporate in my tmux status bar. To give some background, my tests will output to a file called .guard_result with either success or failure and the test_status function reads from that file and echoes a 💚 if my tests are passing and a ❤️ if they are failing.
The good news is running test_status works just fine, I'm just having trouble getting it to work with tmux. What am I missing here?
# ~/.oh-my-zsh/custom/aliases.zsh
function test_status {
if [ ! -f "./.guard_result" ]; then
echo "?"
return 1
fi
result="$(cat ./.guard_result)"
if [[ $result == *"success"* ]]
then
echo "💚";
elif [[ $result == *"fail"* ]]
then
echo "❤️";
fi
}
This function works... Here is Tmux configuration (which doesn't show result):
# ~/.tmux.conf
set -g status-right "#(test_status) #[fg=colour245]%d %b %Y #[fg=white]:: #[fg=colour245]%l:%M %p"
I know I must be missing something simple... Thanks for your help!
tmux passes shell commands to /bin/sh not zsh. And even if tmux would use zsh, the function would not be available in that context as ~/.zshrc, which loads oh-my-zsh, is only read for interactive shells.
In order to get the the output of test_status into tmux, I would suggest to put the function into a zsh script and call that.
You can either source ~/.oh-my-zsh/custom/aliases.zsh from within the script and then call test_status:
#!/usr/bin/zsh
# ^ make sure this reflects the path to zsh (`type zsh`)
source ~/.oh-my-zsh/custom/aliases.zsh
test_status
Or you can just put the entire function into the script, so as to not clutter alias.zsh:
#!/usr/bin/zsh
function test_status {
if [ ! -f "./.guard_result" ]; then
echo "?"
return 1
fi
result="$(cat ./.guard_result)"
if [[ $result == *"success"* ]]
then
echo "💚";
elif [[ $result == *"fail"* ]]
then
echo "❤️";
fi
}
Safe the script somewhere (e.g. /path/to/test_status.zsh), make it executable (chmod a+x /path/to/test_status.zsh) and call it by path in the tmux configuration.

Version Comparison using KSH

I'm trying to write a function to compare the versions of the products.
my versions can be XX.XX.XX or xx-xx-xx either it's separated with "." or "-"
and number of fields can be different either xx.xx or xx.xx.xx or xx.xx.xx.xx
the versions which im gonna compare will identical in delimiters and with the fields
#!/bin/ksh
set -x
compareVersions ()
{
typeset IFS='.'
typeset -a v1=( $1 )
typeset -a v2=( $2 )
typeset n diff
for (( n=0; n<4; n+=1 )); do
diff=$((v1[n]-v2[n]))
if [ $diff -ne 0 ] ; then
[ $diff -le 0 ] && echo '-1' || echo '1'
return
fi
done
echo '0'
} # ---------- end of function compareVersions ----------
#compareVersions "6100-09-03" "6100-09-02"
compareVersions "6100.09.03" "6100.09.02"
Please check and give me suggestions
I have tried with the below thing which i have got a other post.. but there is no luck.. hope there should some modification should be done. I have to use across platforms ( linux, solaris, AIX ) so i have preferred KSH, i have idea only in shell scripting though.
Create arrays from version strings, then loop through them comparing elements one by one and return values accordingly. The following example will compare two version strings and returns either 0 (versions are equal), 1 (the first version string is greater) or 2 (the second version string is greater).
#!/bin/ksh
function vertest {
set -A av1 `echo $1 | sed -e 's/\'$3'/ /g'`
set -A av2 `echo $2 | sed -e 's/\'$3'/ /g'`
for (( i=0; i < ${#av1[#]}; i++ )) ; do
[[ ${av1[$i]} -eq ${av2[$i]} ]] && continue
[[ ${av1[$i]} -gt ${av2[$i]} ]] && return 1
[[ ${av1[$i]} -lt ${av2[$i]} ]] && return 2
done
return 0
}
v1="2-7-2-1"
v2="1-8-0-1"
vertest $v1 $v2 '-'
exit $?
# end of file.
This example will exit to shell with exit code 1. Should you change $v1 to 1-7-2-1, it will exit to shell with exit code 2. And so on, and so forth.
The separator escaping is not complete, but this works with most reasonable separators like a period (.) and a dash (-). This, as well as parameter checking for the vertest() is left as an exercise for the reader.
When the format of both numbers is equal (leading zero as your example), you can use
compareVersions ()
{
val1=$(echo $1| tr -d ".-")
echo ${val1}
val2=$(echo $2| tr -d ".-")
echo ${val2}
if [ ${val1} -gt ${val2} ] ; then
echo 1
return
fi
if [ ${val1} -eq ${val2} ] ; then
echo 0
return
fi
echo '-1'
} # ---------- end of function compareVersions ----------

Resources