Creating autocomplete script with sub commands - fish

I'm trying to create an autocomplete script for use with fish; i'm porting over a bash completion script for the same program.
The program has three top level commands, say foo, bar, and baz and each has some subcommands, just say a b and c for each.
What I'm seeing is that the top level commands auto complete ok, so if I type f I'm getting foo to autocomplete, but then if I hit tab again to see what it's sub commands are, i see foo, bar, baz, a, b, c and it should just be a, b, c
I am using as a reference the git completion script since it seems to work right. I am also using the git flow script as a reference as well.
I think this is handled in the git completion script by:
function __fish_git_needs_command
set cmd (commandline -opc)
if [ (count $cmd) -eq 1 -a $cmd[1] = 'git' ]
return 0
end
return 1
end
Which makes sense, you can only use the completion if there is a single arg to the command, the script itself; if you use that as the condition (-n) for the call to complete on the top level commands, I think the right thing would happen.
However, what I'm seeing is not the case. I copied that function over to my script, changed "git" appropriately, and did not have any luck.
The trimmed down script is as follows:
function __fish_prog_using_command
set cmd (commandline -opc)
set subcommands $argv
if [ (count $cmd) = (math (count $subcommands) + 1) ]
for i in (seq (count $subcommands))
if not test $subcommands[$i] = $cmd[(math $i + 1)]
return 1
end
end
return 0
end
return 1
end
function __fish_git_needs_command
set cmd (commandline -opc)
set startsWith (echo "$cmd[1]" | grep -E 'prog$')
# there's got to be a better way to do this regex, fish newb alert
if [ (count $cmd) = 1 ]
# Is this always false? Is this the problem?
if [ $cmd[1] -eq $cmd[1] ]
return 1
end
end
return 0
end
complete --no-files -c prog -a bar -n "__fish_git_needs_command"
complete --no-files -c prog -a foo -n "__fish_git_needs_command"
complete --no-files -c prog -a a -n "__fish_prog_using_command foo"
complete --no-files -c prog -a b -n "__fish_prog_using_command foo"
complete --no-files -c prog -a c -n "__fish_prog_using_command foo"
complete --no-files -c prog -a baz -n "__fish_git_needs_command"
Any suggestions on how to make this work is much appreciated.

I guess you are aware that return 0 means true and that return 1 means false?
From your output it looks like your needs_command function is not working properly, thus showing bar even when it has subcommands.
I just tried the following code and it works as expected:
function __fish_prog_needs_command
set cmd (commandline -opc)
if [ (count $cmd) -eq 1 -a $cmd[1] = 'prog' ]
return 0
end
return 1
end
function __fish_prog_using_command
set cmd (commandline -opc)
if [ (count $cmd) -gt 1 ]
if [ $argv[1] = $cmd[2] ]
return 0
end
end
return 1
end
complete -f -c prog -n '__fish_prog_needs_command' -a bar
complete -f -c prog -n '__fish_prog_needs_command' -a foo
complete -f -c prog -n '__fish_prog_using_command foo' -a a
complete -f -c prog -n '__fish_prog_using_command foo' -a b
complete -f -c prog -n '__fish_prog_using_command foo' -a c
complete -f -c prog -n '__fish_prog_needs_command' -a baz
Output from completion:
➤ prog <Tab>
bar baz foo
➤ prog foo <Tab>
a b c
➤ prog foo
Is this what you want?

Related

ZSH autocomplete function using existing autocompletions

I have a function mycmd to launch a program that I wrote. The program needs the first argument to be foo, ssh or ls. The second argument depends on the first argument as follows,
foo -> No second argument
ssh -> Something to ssh to
ls -> A file
I want to write zsh autocomplete function for mycmd which suggest the second argument depending on the first argument. In the simplest form, I know that I can do the following for the first argument
_mycmd() {
compadd foo ssh ls
}
compdef _mycmd mycmd
I have a hard time understanding what to do for the second argument from here. How do I use _ssh autocomplete for ssh argument and _ls autocomplete for ls argument? (And nothing for foo as well)
To inspect the current command line, it could be used $words and $CURRENT that are the completion special parameters.
CURRENT
This is the number of the current word, i.e. the word the cursor is currently on in the words array.
...
words
This array contains the words present on the command line curently being edited.
--- zshcompwid(1), completion special parameters, zshcompwid - zsh completion widgets
The completion function could modify $words and $CURRENT (and/or other variables) and then start the entire completion system based with its modified command line. For example:
$ mycmd ls -al<TAB> ;# This is the input, and
;# $words == ("mycmd" "ls" "-al") ;# original value for $words.
;# $words=("ls" "-al") ;# We could update $words for making zsh
;# $ ls -al<TAB> ;# to start the completion system with
;# its modified command line.
;# Finally, _ls would be called.
The utility function _normal could be used.
_normal
...
A second use is to reexamine the command line specified by the $words array and the $CURRENT parameter after those have been modified.
-- zshcompsys(1), utility function, _normal
_mycmd could be listed below:
_mycmd () {
if ((CURRENT == 2)); then
compadd foo ssh ls
elif ((CURRENT > 2)); then
case "$words[2]" in
(ssh|ls)
shift words
((CURRENT--))
_normal -p mycmd
;;
(foo)
_nothing
;;
(*)
_message "mycmd: invalid subcommand or arguments"
;;
esac
fi
return $?
}
or even, more use of the completion builtin/utility functions like below:
_mycmd () {
local curcontext="${curcontext}" state context line
local -A opt_args
_arguments '*:: :->subcmd'
if [[ "$state" == "subcmd" ]]; then
if ((CURRENT == 1)); then
_describe -t mycmd-subcmd "mycmd command" '(foo ssh ls)'
else
curcontext="${curcontext%:*:*}:mycmd-$words[1]:"
case "$words[1]" in
(ssh|ls)
compset -n 1
_normal -p $service
;;
(foo)
_nothing
;;
(*)
_message "mycmd: invalid subcommand or arguments"
;;
esac
fi
fi
return $?
}

How to automate the LSF waiting based on job name in perl

I have a perl code where I am submitting few jobs at once in parallel via LSF bsub command and once all these jobs finish want to submit a final job.
For example I have these three bsub commands where first two bsub commands submits jobs t1 and t2 and third command checks whether t1 and t2 are finished or not and wait on them with -w argument.
system(" bsub -E "xyz" -n 1 -q queueType -J t1 sleep 30")
system("bsub -E "xyz" -n 1 -q queueType -J t2 sleep 30")
system("bsub -E "xyz" -n 1 -q queueType -J t3 -w "done(t1)&&done(t2)" sleep 30")
So for automating -w argument I have this
my $count=2;
my $i;
system(" bsub -E "xyz" -n 1 -q queueType -J t3 \"foreach my $i (0..$count) {print " done(t_$i)&&";}\" sleep 30 ")
I get this error:
sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `bsub -E "/pkg/ice/sysadmin/bin/linux-pre-exec" -n 1 -q short -J t3 -w "foreach (0..7) {print \"done(t)&&\";}" sleep 30'
EDIT: Yes I am using system command to submit these jobs from perl
If you want to generate the done(...)&&done(...) string dynamically, you can use
my $count = 7;
my $done_all = join '&&', map "done(t$_)", 1 .. $count;
That is, for each number in the range 1 .. 7, produce a string "done(t$_)", which gives a list "done(t1)", "done(t2)", ... "done(t7)". The elements of this list are then join'd together with a separator of &&, producing "done(t1)&&done(t2)&&...&&done(t7)".
To run an external command, you can use system. Passing a list to system avoids going through the shell, which avoids all kinds of nasty quoting issues:
system('bsub', '-E', 'xyz', '-n', '1', '-q', 'queueType', '-J', 't3', '-w', $done_all, 'sleep', '30');
# or alternatively:
system(qw(bsub -E xyz -n 1 -q queueType -J t3 -w), $done_all, qw(sleep 30));
Your code tries to pass Perl code to bsub, but that won't work. You have to generate the command string beforehand and pass the result to bsub.

Zsh executing two commands in a row independent of success

I would like to execute two commands in a row independent of the failure or success of the previous one, so I know that || and && will not work. What can I do in this case? I would like to have the shell wait for the first command to finish if it is successful; hence ; does not work either.
EDIT: I apologize the shell would be zsh and I run a shell script sending commands to different screens as seen below:
#! /bin/zsh
### Script for running everything in screens ###
### System argument screen name suffix ###
echo You have the following screens running:
screen -ls
sigarr=(NM1 NM2 NM3 Scenario4 Scenario6)
puarr=(50PU 140PU)
lumarr=(30 300 3000)
echo Please type 1 for 50PU samples and 2 for 140PU samples
read PU
if [[ $PU -ne 1 && $PU -ne 2 ]] ; then
echo You have to enter 1 or 2
return 1
fi
echo Please type 1 for 300fb-1 and 2 for 3000fb-1
read lum
if [[ $lum -ne 1 && $lum -ne 2 ]] ; then
echo You have to enter 1 or 2
return 1
fi
if [ $PU = 1 ]; then
let "lum = $lum + 1"
#echo $lum
fi
ex NEWrunReader.py <<EOEX
:43s/Lumi.*/Lumi=$lumarr[lum]/
:x
EOEX
echo Compiling the reader file!!!
root -l << EOF
.L readerSummerStd.C+
EOF
if [ $PU = 2]; then
let "lum = $lum + 1"
fi
echo Press any key to proceed or Ctrl+C to abort!
read
for sigind in $sigarr
do
screen -dmS "${sigind}_${lumarr[lum]}_${puarr[PU]}_${1}"
sleep 0.1
screen -S "${sigind}_${lumarr[lum]}_${puarr[PU]}_${1}" -p 0 -X stuff "./NEWrunReader.py SummerStd $puarr[PU]_$sigind $1 >& "${sigind}_${lumarr[lum]}_${1}".txt &;exit"$'\r'
done
return 0
Use | or & instead of usng || or &&
for zsh, especially on a mac, it's ;
try it from the terminal
sleep 3 ; echo "hello"
There will be a 3 seconds delay then it will print hello

comparison the content of a text and csv files

I need a sample bash script to compare a first line of a file(Result.txt) to first row and column of another file(table.csv), then send the result to an html file.
I am very basic in coding, this is what I found so far:
#!/bin/sh
Result.txt="$(head -n 1 < $1|tail -n 1)"
table.csv="$(head -n 1 < $2|tail -n 1)"
test "$R.txt" = "$sheet.csv" && (echo The same; exit 0)
Appreciate your help
Slightly tweaking your script.
#!/bin/bash
Res=$(head -n 1 "$1")
tab=$(head -n 1 "$2")
[[ $Res == $tab ]] && echo The same
Notes
"dot" is not a valid identifier (i.e. variable name) character: valid is letters, numbers and underscore, and the first character cannot be a number.
if you're doing head -1, there's no need to pipe that into tail -1
I think [[ is more readable than test, primarily because [[ forces you to have ]]
parentheses launch a subshell which is overkill for an echo statement.
the exit will only exit the subshell not your program
if you have multiple statements, use if ...; then ...; fi -- it's more readable.

svn diff through perltidy

I want to run perltidy before i look for diff in my subversion working copy.
in svn config i wrote:
diff-cmd = /usr/bin/d.sh
As David W said in this answer https://stackoverflow.com/a/5834900/1927848 i make a script /usr/bin/d.sh:
#!/usr/local/bin/bash
/usr/local/bin/perltidy "$1" > "/tmp/$1"
/usr/local/bin/perltidy "$2" > "/tmp/$2"
/usr/bin/diff "$1" "$2"
/bin/rm "/tmp/$1" "/tmp/$2"
exit 0
and when i make svn diff in working copy i got some errors:
dev# svn diff
Index: nodeny/new_month.pl
===================================================================
Unknown option: u
Error on command line; for help try 'perltidy -h'
Option l is ambiguous (libpods, line-up-parentheses, logfile, logfile-gap, long-block-line-count, look-for-autoloader, look-for-hash-bang, look-for-selfloader)
Error on command line; for help try 'perltidy -h'
diff: option requires an argument -- L
/usr/bin/diff: Try `/usr/bin/diff --help' for more information.
where is my errors?
UPD: $1 and $2 are not file names, $6 and $7 contains file names. i made some modifications to code, thanks to ikegami comment
#!/usr/local/bin/bash
/usr/local/bin/perltidy "$6" -st > "/tmp/tidy001"
/usr/local/bin/perltidy "$7" -st > "/tmp/tidy002"
/usr/bin/diff "/tmp/tidy001" "/tmp/tidy002"
/bin/rm "/tmp/tidy001" "/tmp/tidy002"
exit 0
but now script only does first perltidy command and wait... whats wrong?
UPD2: perl script, that works:
#!/usr/bin/perl
use Text::Diff;
if (-e $ARGV[-2] && -e $ARGV[-1]) {
my $str1 = `/usr/local/bin/perltidy -npro -pbp -nst -se -et=4 -bar -l=200 $ARGV[-2] -st`;
my $str2 = `/usr/local/bin/perltidy -npro -pbp -nst -se -et=4 -bar -l=200 $ARGV[-1] -st`;
my $diff = diff(\$str1, \$str2);
print $diff;
}
else {
print "Error file $ARGV[-2] or $ARGV[-1] not exists\n";
}
exit 0;
I'm not an experienced bash code, so the following may not be optimal, especially given the redundancy, but it solves your problem by assuming the last two args are the file names.
#!/bin/bash
args=("$#")
f1_idx=$(( ${#args[#]} - 2 ))
f1="${args[$f1_idx]}"
/usr/local/bin/perltidy "$f1" -st > "/tmp/$f1"
args[$f1_idx]="/tmp/$f1"
f2_idx=$(( ${#args[#]} - 1 ))
f2="${args[$f2_idx]}"
/usr/local/bin/perltidy "$f2" -st > "/tmp/$f2"
args[$f2_idx]="/tmp/$f2"
/usr/bin/diff "$args[#]"
/bin/rm "/tmp/$f1" "/tmp/$f2"
exit 0
Or if you don't care about the actual file names (as your update implies), you can avoid the temporary files altogether.
#!/bin/bash
args=("$#")
last_idx=$(( ${#args[#]} - 1 ))
f2="${args[$last_idx]}"
unset args[$last_idx]
last_idx=$(( ${#args[#]} - 1 ))
f1="${args[$last_idx]}"
unset args[$last_idx]
/usr/bin/diff "$args[#]" \
<( /usr/local/bin/perltidy "$f1" -st ) \
<( /usr/local/bin/perltidy "$f2" -st )
exit 0