Use colon as filename separator in zsh tab completion - autocomplete

I have programs which take filename arguments as
scp:path/to/file.ext
ark:/abs/path/to/file.ext
Is it possible make zsh complete filenames after some keywords followed by colon?
So far, I found how to do it in bash: add : to the COMP_WORDBREAKS variable.
Thanks to Gilles, I manage to work like this.
$ cat ~/.zshrc
...
function aftercolon() {
if compset -P 1 '*:'; then
_files "$expl[#]"
else
_files "$expl[#]"
fi
}
autoload -Uz compinit
compinit
compdef aftercolon hello olleh
Now in commands hello and olleh, completion after : works as expected.
I think there might be better way since:
I had to odd if/else since the commands also take filename without prefix.
And since I have many commands take this kind of argument, I need to add names of every commands

Call compset -P 1 '*:' to remove everything up to the first colon, then _files to complete what comes after the colon. For example, if the file name completions don't depend on the part before the colon:
if compset -P 1 '*:'; then
_files "$expl[#]"
else
compadd "$expl[#]" -S : ark scp
fi
If the file name completions depend on the part before the colon, save that from $PREFIX first.
if [[ $PREFIX = *:* ]]; then
local domain=${PREFIX%%:*}
compset -P 1 '*:'
case $domain in
ark) _files "$expl[#]" -g "*.((tar|cpio)(|.gz|.xz|.bz2)|tgz|zip|rar|7z)";;
*) _files "$expl[#]";;
esac
else
compadd "$expl[#]" -S : ark scp
fi

Related

How to execute this command in systemd servicefile?

Ok, so I have this command that turns off my touchscreen. It works when I execute it in a root shell.
So this works:
sudo su
/usr/bin/echo $(ls /sys/bus/hid/drivers/hid-multitouch | awk NR==1'{print $1}') > /sys/bus/hid/drivers/hid-multitouch/unbind
And then my touchscreen stops working, which is the result that I wanted.
Now I want to make a touchscreen.service file to execute this on every boot. So in the service file I include:
ExecStart=/usr/bin/echo $(ls /sys/bus/hid/drivers/hid-multitouch | awk NR==1'{print $1}') > /sys/bus/hid/drivers/hid-multitouch/unbind
However it isn't working > nor throwing any errors that I've been able to catch.
I do know from earlier fidlings with .service files that I might actually need to use /usr/bin/sh -c, so I have also tried:
ExecStart=/usr/bin/sh -c "/usr/bin/echo $(ls /sys/bus/hid/drivers/hid-multitouch | awk NR==1'{print $1}') > /sys/bus/hid/drivers/hid-multitouch/unbind"
Yet this also doesn't work.. maybe because of the awk NR==1'{print $1}'part? I have also tried replacing it with awk NR==1'\''{print $1}'\''but again it fails to work.
Does anyone have any ideas on how to get the command that is working in my root cli environment to also work as a systemd service?
To start with,
The syntax of the awk command is just wrong. The quotes are incorrectly placed. The part NR == 1 is part of the awk command to indicate the first line record in the file, i.e.
awk NR==1'{print $1}'
# ^^^^^^^ should be within quotes
awk 'NR == 1 { print $1 }'
Your sequence of echo, ls and the command substitution $(..) doesn't look right. You are effectively echo-ing the literal string /sys/bus/hid/drivers/hid-multitouch (if ls finds the file at that path) over to the pipe and awk just writes that to the /sys/bus/hid/drivers/hid-multitouch/unbind file which might not be your desired action. You just needed to do run the command on the file directly as
awk 'NR == 1 { print $1 }' /sys/bus/hid/drivers/hid-multitouch > /sys/bus/hid/drivers/hid-multitouch/unbind
Now that, that the awk command is fixed, you have two options to run the above command as part of systemd, either put your command in a script or run the command directly. For putting it in a script refer to the Unix.SE answer Where do I put scripts executed by systemd units?. As for running the command directly in ExecStart. Aside from using /bin/sh also use the path /bin/awk
So putting it together and using /bin/ over /usr/bin, you can do below. This command uses ".." over awk script and needs escape of $1
ExecStart=/bin/sh -c '/bin/awk "NR == 1 { print \$1 }" /sys/bus/hid/drivers/hid-multitouch > /sys/bus/hid/drivers/hid-multitouch/unbind'

How can I edit crontabs in VS Code?

If I try to use Visual Studio Code (on macOS 10.15) to edit my crontab, it opens an empty file without the contents of my crontab.
$ VISUAL='code' crontab -e
crontab: no changes made to crontab
I didn't actually expect this to work (without -w) but include it for completeness. But when I add the -w it still fails.
$ VISUAL="code -w" crontab -e
crontab: code -w: No such file or directory
crontab: "code -w" exited with status 1
It occurred to me that there may be some weirdness with quoting, but neither single quotes nor the following fixed anything:
$ function codew() {
function> code -w "$1"
function> }
$ export VISUAL='codew'
$ crontab -e
The problem seems to be that the crontab's tempfile is not actually present. But how do I solve this? How can I use VS Code to edit crontabs?
Create a file touch ~/code-wait.sh:
#!/bin/bash
OPTS=""
if [[ "$1" == /tmp/* ]]; then
OPTS="-w"
fi
/usr/local/bin/code ${OPTS:-} -a "$#"
Make this file executable:
chmod 755 ~/code-wait.sh
Add to your .bashrc or .bash_profile or .zshrc:
export VISUAL=~/code-wait.sh
export EDITOR=~/code-wait.sh
Run command:
EDITOR='code' crontab -e
here the setting works for me.
.bashrc
## vscode
export VISUAL=/path/to/code-wait.sh
export EDITOR=/path/to/code-wait.sh
code-wait.sh
#!/bin/sh
code -w $*
That is quite a complex issue because there is no way to detect which tool calls the preferred editor. The TTY is the same and no environment variables can help.
Still, I was able to come up with a solution that enables the foreground mode (wait) for temporary files. IMHO, most if not all tools that use external editors and are waiting for them to save the file do use temporary files.
Full script is at https://github.com/ssbarnea/harem/blob/master/bin/edit but I will include here the main snippet:
#!/bin/bash
OPTS=""
if [[ "$1" == /tmp/* ]]; then
OPTS="-w"
fi
/usr/local/bin/code ${OPTS:-} -a "$#"

Get fish shell to work with gcloud command line tools?

Has anyone had any luck getting fish shell to work with google's gcloud command line tools? I'm not an expert in Fish script but these are the two files gcloud needs to run (which work fine use Fish's bash mode). Fish doesn't allow you to source bash files from what I understand so these would need to be converted to Fish script?
path.bash
script_link="$( readlink "$BASH_SOURCE" )" || script_link="$BASH_SOURCE"
apparent_sdk_dir="${script_link%/*}"
if [ "$apparent_sdk_dir" == "$script_link" ]; then
apparent_sdk_dir=.
fi
sdk_dir="$( command cd -P "$apparent_sdk_dir" && pwd -P )"
bin_path="$sdk_dir/bin"
export PATH=$bin_path:$PATH
path.completion
_python_argcomplete() {
local IFS=''
COMPREPLY=( $(IFS="$IFS" COMP_LINE="$COMP_LINE" COMP_POINT="$COMP_POINT" _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" _ARGCOMPLETE=1 "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) )
if [[ $? != 0 ]]; then
unset COMPREPLY
fi
}
complete -o default -F _python_argcomplete "gcloud"
_completer() {
command=$1
name=$2
eval '[[ "$'"${name}"'_COMMANDS" ]] || '"${name}"'_COMMANDS="$('"${command}"')"'
set -- $COMP_LINE
shift
while [[ $1 == -* ]]; do
shift
done
[[ $2 ]] && return
grep -q "${name}\s*$" <<< $COMP_LINE &&
eval 'COMPREPLY=($'"${name}"'_COMMANDS)' &&
return
[[ "$COMP_LINE" == *" " ]] && return
[[ $1 ]] &&
eval 'COMPREPLY=($(echo "$'"${name}"'_COMMANDS" | grep ^'"$1"'))'
}
unset bq_COMMANDS
_bq_completer() {
_completer "CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=1 bq help | grep '^[^ ][^ ]* ' | sed 's/ .*//'" bq
}
unset gsutil_COMMANDS
_gsutil_completer() {
_completer "CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=1 gsutil help | sed /Additional/q | grep '^ ' | sed -e 's/^ //' -e 's/ .*//'" gsutil
}
unset gcutil_COMMANDS
_gcutil_completer() {
_completer "CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=1 gcutil help | grep -v '^information' | grep '^[a-z]' | sed -e 's/ .*//' -e '/^$/d'" gcutil
}
complete -o default -F _bq_completer bq
complete -o default -F _gsutil_completer gsutil
complete -o default -F _gcutil_completer gcutil
What worked for me was just using bass. Check it out:
https://github.com/edc/bass
Just take the lines that gcloud adds to your bash_profile, and prepend bass to them in your .config/fish/config.fish file, as follows:
# The next line updates PATH for the Google Cloud SDK.
bass source '/Users/hunter/bin/google-cloud-sdk/path.bash.inc'
# The next line enables shell command completion for gcloud.
bass source '/Users/hunter/bin/google-cloud-sdk/completion.bash.inc'
As of today, I was able just to do
brew install --cask google-cloud-sdk
Added source /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.fish.inc to my ~/.config/fish/config.fish
Clone https://github.com/aliz-ai/google-cloud-sdk-fish-completion then run install.sh.
For path.bash, all it does is add the Cloud SDK bin directory to your PATH. We put some weird stuff in there because we wanted it to work from inside the Cloud SDK directory even when behind, eg, a symlink. For your own system, just do the fsh equivalent of "export PATH=$PATH:/path/to/google-cloud-sdk/bin".
For the tab completion, I don't know how fsh's tab completion works, so I've got nothing.
Fish support is now included out of the box with gcloud, however I ran into a pretty annoying issue. The code included in google-cloud-sdk/path.fish.inc (and #nafg's answer) leaves the directory changed, resulting in each new shell session starting in the google-cloud-sdk directory.
The modification I made was fairly simple, adding two extra lines to get the current working directory and restore it afterwards. This seems to have resolved the issue for me, so hopefully will help anyone else googling for "fish gcloud" problems.
set restore_dir (pwd -P)
set sdk_dir (builtin cd "$apparent_sdk_dir" > /dev/null; and pwd -P)
set bin_path "$sdk_dir/bin"
cd "$restore_dir"
I was able to set up completion by executing this:
# fisher v3
fisher add aliz-ai/google-cloud-sdk-fish-completion
# fisher v4
fisher install aliz-ai/google-cloud-sdk-fish-completion
Fisher can be found here: https://github.com/jorgebucaran/fisher
using fisher:
fisher install lgathy/google-cloud-sdk-fish-completion
and you are good to go
There's an interesting approach here: http://michelpm.com/blog/2013/07/26/switching-from-zsh-to-fish/
Basically it will run a bash script in bash, but it will diff how it changes the environment and apply that in fish.
However it won't work for completions and for your path.bash it's overkill. More like:
Change var=value to set var value
Change [ ... ] to test ...
Change $( ... ) to ( ... )
if doesn't need then and ends with end
Change || to ; or and && to ; and
Change export to set -x
So without testing here's what I would try:
set script_link ( readlink "$BASH_SOURCE" ); or set script_link $BASH_SOURCE
set apparent_sdk_dir ${script_link%/*}
if test "$apparent_sdk_dir" == "$script_link" ;
set apparent_sdk_dir .
end
set sdk_dir ( command cd -P "$apparent_sdk_dir"; and pwd -P )
set bin_path $sdk_dir/bin
set -x PATH $bin_path:$PATH

How can I make a shell script indicate that it was successful?

If I have a basic .sh file containing the following script code:
#!/bin/sh
rm -rf "MyFolder"
How do I make this running script file display results to the terminal that will indicate if the directory removal was successful?
You don't really need to make it say it was successful. You could have it say something only on error ✖, and then silence means success ✔.
That's how the Unix philosophy works:
The rule of silence, also referred to as the silence is golden rule, is an important part of the Unix philosophy that states that when a program has nothing surprising, interesting or useful to say, it should say nothing. It means that well-behaved programs should treat their users' attention and concentration as being valuable and thus perform their tasks as unobtrusively as possible. That is, silence in itself is a virtue. http://www.linfo.org/rule_of_silence.html
That's the way rm itself behaves.
If you are asking about the general case, as suggested by your question's title, you can run your script with sh -x scriptname to see what it's doing. It's also quite common to write diagnostic output into the script itself, and control it with an option.
#!/bin/sh
verbose=false
case $1 in -v | --verbose )
verbose=true
shift ;;
esac
say () {
$verbose || return
echo "$0: $#" >&2
}
say "Removing $dir ..."
rm -rf "$dir" || say "Failed."
If you run this script without any options, it will run silently, like a well-behaved Unix utility should. If you run it with the -v option, it will print some diagnostics to standard error.
rm -rf "My Folder" && echo "Done" || echo "Error!"
You can read more on creating a sequence of pipelines in bash manual
In the bash (and other similar shells) the ? environment variable gives you the exit code of the last executed command. So you can do:
#!/bin/sh
rm -rf "My Folder"
echo $?
UPDATE
If once the rm command has been executed the directory doesn't exist (because it has been successfully removed or because it didn't exist when the command was executed) the script will print 0. If the directory exists (which will mean that the command has been unable to remove it) then the script will print an exit code other than 0. If I understand properly the question this is exactly the requested behavior. If it is not, please correct me.
The previous answers was wrong : rm don't exit with error code > 0 when the dir isn't present.
Instead, I recommend to use :
dir='/path/to/dir'
if [[ -d $dir ]]; then
rm -rf "$dir"
fi
If you want rm to return a status, remove -f flag.
Example on Linux Mint (the dir doesn't exists):
$ rm -rf /tmp/sdfghjklm
$ echo $?
0
$ rm -r /tmp/sdfghjklm
$ echo $?
1

Check if program is in path

Can sh itself check if a program exists or is in path?
I.e., not with the help of the "which" program.
I don't believe sh can directly. But perhaps something like:
which() {
save_IFS=$IFS
IFS=:
for d in $PATH; do
test -x $d/$1 && echo $d/$1
done
IFS=$save_IFS
}
and here's a nice variation that uses a subshell so that restoring IFS is not necessary:
which() (
IFS=:
for d in $PATH; do
test -x $d/$1 && echo $d/$1
done
)
Also, (in bash) if the command has been executed in the past and bash has already done the PATH search, you can see what it found with hash -t.
bash-3.2$ hash -t which
bash: hash: which: not found
bash-3.2$ which foo
bash-3.2$ hash -t which
/usr/bin/which
The utility command -v $CMD is apparently a portable option (in the sense of being part of POSIX); see also the very similar (though bash-specific) question, in particular this answer.