Is ( ... ) a subshell in this code or something else? - sh

In this POSIX shell function of mine:
disable_mouse_for_a_second()
{
if xinput --disable "$1" 2> /dev/null
then
(
sleep 1s
xinput --enable "$1"
) &
return 0
else
return 1
fi
}
Is ( ... ) a subshell in this code or something else?

The short answer to your question is Yes. The reason why is the block in question is nothing more than the single line:
( sleep 1s; xinput --enable "$1"; ) &
spread over multiple lines. It simply executes (and backgrounds) the sleep and xinput commands.
The two primary compound commands written over multiple lines you will see are:
(list) ## and
{ list; }
The distinction between the two is (list) is executed in a subshell environment and variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes.
{ list; } list is simply executed in the current shell environment.

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 $?
}

Separate command-line arguments into two lists and pass to programs (shell)

What I want to do is take a list of command-like arguments like abc "def ghi" "foo bar" baz (note that some arguments are quoted because they contain spaces), and separate them out into two lists of arguments which then get passed to other programs that are invoked by the script. For example, odd-numbered arguments to one program and even-numbered arguments to another program. It is important to preserve proper quoting.
Please note, I need a solution in pure Bourne Shell script (i.e., sh not bash or such). The way I'd do this in Bash would be to use arrays, but of course the Bourne Shell doesn't have support for arrays.
At the cost of iterating over the original arguments twice, you can define a function that can run a simple command using only the even or odd arguments. This allows us to use the function's arguments as an additional array.
# Usage:
# run_it <cmd> [even|odd] ...
#
# Runs <cmd> using only the even or odd arguments, as specified.
run_it () {
cmd=${1:?Missing command name}
parity=${2:?Missing parity}
shift 2
n=$#
# Collect the odd arguments by discarding the first
# one, turning the odd arguments into the even arguments.
if [ $# -ge 1 ] && [ $parity = odd ]; then
shift
n=$((n - 1))
fi
# Repeatedly move the first argument to the
# to the end of the list and discard the second argument.
# Keep going until you have moved or discarded each argument.
while [ "$n" -gt 0 ]; do
x=$1
if [ $n -ge 2 ]; then
shift 2
else
shift
fi
set -- "$#" "$x"
n=$((n-2))
done
# Run the given command with the arguments that are left.
"$cmd" "$#"
}
# Example command
cmd () {
printf '%s\n' "$#"
}
# Example of using run_it
run_it cmd even "$#"
run_it cmd odd "$#"
This might be what you need. Alas, it uses eval. YMMV.
#!/bin/sh
# Samples
foo() { showme foo "$#"; }
bar() { showme bar "$#"; }
showme() {
echo "$1 args:"
shift
local c=0
while [ $# -gt 0 ]; do
printf '\t%-3d %s\n' $((c=c+1)) "$1"
shift
done
}
while [ $# -gt 0 ]; do
foo="$foo \"$1\""
bar="$bar \"$2\""
shift 2
done
eval foo $foo
eval bar $bar
There's no magic here -- we simply encode alternating arguments with quote armour into variables so they'll be processed correctly when you eval the line.
I tested this with FreeBSD's /bin/sh, which is based on ash. The shell is close to POSIX.1 but is not necessarily "Bourne". If your shell doesn't accept arguments to shift, you can simply shift twice in the while loop. Similarly, the showme() function increments a counter, an action which can be achieved in whatever way is your favourite if mine doesn't work for you. I believe everything else is pretty standard.

How to define a function that either takes arguments or doesnt?

I am using Fish shell....
Basically, to do something like this:
if (first argument == --r) {
do something
} else {
Do something
if (first argument == --n) {
do more
}
}
To achieve the first if statement I tried:
if test (count $argv) -eq 1 -a $argv[1] = '--r'
But that gives a message:
test: Missing argument at index 6
Functions in Fish don't require their parameters to be specified when you define the function. Any arguments sent to the function by the user are automatically stored in an array called argv. In order to determine whether arguments were sent, you can either count the number of elements in the array, or determine the length of the array as a string. I do the latter:
function my_func
if [ -z "$argv" ]; # No arguments
echo "No arguments supplied"
return
else # At least one argument
if [ "$argv[1]" = "--r" ];
echo "Excellent!"
return
end
end
end
If you prefer to use count, then it will look more like this:
function my_func
if [ (count $argv) -eq 1 -a "$argv[1]" = "--r" ];
# Exactly one argument with specified value "--r"
echo "Excellent!"
return
else # May have arguments, but none equal to "--r"
echo "Give me the right arguments"
return
end
end
Your use of set -q argv[1] is also a good option. But when you're checking for string equality, don't forget to surround your variable in quotes, like this: test "$argv[1]" = "--r".
Here's another method, using the switch...case conditional test:
function my_func
# No arguments
if [ -z "$argv" ]; and return
# At least one argument
switch $argv[1];
case --r;
# do some stuff
return
case "*";
# Any other arguments passed
return
end
end
end
This worked for me:
if set -q argv[1] ;and test $argv[1] = "--r"
Let's start with the error you get when executing this:
if test (count $argv) -eq 1 -a $argv[1] = '--r'
That happens because fish first expands $argv[1] then executes test. If argv has no values then that statement turns into
if test 0 -eq 1 -a = '--r'
Which isn't valid syntax for the test command. It doesn't matter that the first sub-expression evaluates to false since test parses the entire expression before evaluating it.
Rather than doing test (count $argv) -eq 1 just do set -q argv[1] to test if argv has at least one argument. Note the lack of a dollar-sign.
If you're using fish 2.7.0 or newer I recommend the new argparse builtin for handling arguments. Several of the standard functions that ship with fish use it so you can look at them, as well as man argparse, for examples of how to use it. Using argparse is almost always safer, less likely to result in bugs due to sloppy argument parsing using hand written fish script, and will provide argument parsing semantics identical to most commands including all the fish builtins. Including correctly handling short and long flags.
well if the argument is optional then you can do this by:
//check if variable exists
if (typeof variable === 'undefined'){
}
else{
if(typeof variable){}
}

Using Perl Net::OpenSSH to sudo test if a file exists, using command concatenation

Simple question, how can I sudo test if a file exists using Net::OpenSSH? I really tried hard to figure this out, but with no result. Ok, I can use a wrapper script, but that's not what I want.
This is my wrapper sub. It works fine most of the time, but it's not testing for files?!
sub sudo {
my $f = shift;
my $h = shift;
my $cmd = shift;
my #out = $f->{ssh}->capture(
{ stdin_data => ["$f->{pwd}\n", #$h ] },
'/usr/bin/sudo','-Sk','-p','','--',
#{$cmd}
);
return \#out;
}
&sudo($f, [], ['test', '-e', '/user/local/bin/cmd', '&&', 'echo', '1', '||', 'echo', '0']); # <= fails
&sudo($f, [], ['test -e /user/local/bin/cmd && echo 1 || echo 0']); # <= fails too
... # arbitrary other $str/$array combinations ...
I know, I don't have to sudo check a cmd in /usr/local/bin , it's just an example. Of course I also "chomped" the result after &sudo().
such a simple task ... and I'm stuck!
It seems I can't get the command concatenation working: there is an extra argument && warning. The sub always returns undef.
Is this a limitation? Is this supported at all? Using
$f->{ssh}->capture('test -e /user/local/bin/cmd && echo 1 || echo 0');
works fine, which is what I'm using right now. So an additional sudo in front should not be that much of a problem ...
Can anyone help?
The command supplied to sudo works similar like the list form of perl's system() or exec() — that is, without using any shell. So any shell metacharacters like && or || won't work here. To fix this, explicitly use a shell:
sudo($f, [], ['sh', '-c', 'test -e /user/local/bin/cmd && echo 1 || echo 0']);
Alternatively, instead of using capture() you could use Net::OpenSSH's system() instead and just inspect the return value — then there's no need for using && and ||.

How do I output the current history event number (%! or %h) -1 in my ZSH prompt?

In my ZSH prompt I want to be able to output the current history event number (%! or %h) -1
If current history event number is !256, I want to subtract it by one and output the result in my prompt (i.e. !255).
Here's the way it looks now and how I'd like it to be:
Below is my current ZSH theme (and the code pertaining to this question lies in the previous_history_event_number () function, which is triggered from the return_code_enabled= declaration:
# ------------------------------------------------------------------------------
# FILE: hced.zsh-theme
# DESCRIPTION: oh-my-zsh theme file.
# (Credits to authors of blinks.zsh-theme and dieter.zsh-theme
# from which themes I've taken useful bits.)
# AUTHOR: hced
# VERSION: 0.0.1
# SCREENSHOT:
# ------------------------------------------------------------------------------
function _prompt_char() {
if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
echo "%{%F{blue}%}±%{%f%k%b%}"
else
echo ' '
fi
}
ZSH_THEME_GIT_PROMPT_PREFIX=" [%{%B%F{blue}%}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%{%f%k%b%K{black}%B%F{green}%}]"
ZSH_THEME_GIT_PROMPT_DIRTY=" %{%F{red}%}*%{%f%k%b%}"
ZSH_THEME_GIT_PROMPT_CLEAN=""
PROMPT='%{%f%k%b%}
%{%K{black}%B%F{green}%}%n%{%B%F{blue}%}#%{%B%F{cyan}%}%m%{%B%F{green}%} %{%b%F{yellow}%K{black}%}%~%{%B%F{green}%}$(git_prompt_info)%E%{%f%k%b%}
%{%K{black}%}$(_prompt_char)%{%K{black}%} %#%{%f%k%b%} '
#RPROMPT='!%{%B%F{cyan}%}%!%{%f%k%b%}'
function previous_history_event_number () {
prev_hist_num=("%!"-1)
declare -i prev_hist_num
echo "$prev_hist_num gave exit code: "
}
# elaborate exitcode on the right when >0
return_code_enabled="%(?..%{$fg[red]%}$(previous_history_event_number)%?%{$reset_color%})"
return_code_disabled=
return_code=$return_code_enabled
RPS1='${return_code} !%{%B%F{cyan}%}%!%{%f%k%b%}'
function accept-line-or-clear-warning () {
if [[ -z $BUFFER ]]; then
time=$time_disabled
return_code=$return_code_disabled
else
time=$time_enabled
return_code=$return_code_enabled
fi
zle accept-line
}
zle -N accept-line-or-clear-warning
bindkey '^M' accept-line-or-clear-warning
Disclaimer: I'm not a programmer so the code within the previous_history_event_number () function is pretty (vastly) clueless right now.
If current history event number is shown when you add %! atom in a prompt then you can get it into a variable by using (%) modifier:
V='%!'
integer HISTORY_EVENT_NUMBER=${(%)V}-1