How to make an alias to enclose multi-member expression passed as argument to a perl script - zsh

I spent a long time searching but cannot find the answer.
I am using an "astronomy-aware" perl script which is super useful for calculations on the command line and in scripts. The problem is that parentheses have to be escaped:
calc.pl (1+1)/(2+2)
zsh: unknown file attribute: 2
calc.pl \(1+1\)/\(2+2\)
0.5
The best alternative to escaping each one is using single quotes to enclose the entire expression like this:
calc.pl '(1+1) / (2+2)'
0.5
How can I define a zsh alias (like alias calc="${HOME}/bin/calc.pl") that encloses the expression that comes after the call to the script within the single quotes as shown in the second example?

The solution linked by Barmar works! Thank you so much!
It was provided by (aloxaf) here:
https://superuser.com/questions/1508079/auto-quote-arguments-in-zsh
I defined the following in my .zshrc and it works.
function quote-accept-line() {
local -a starts_with=("calc.pl ")
for str ($starts_with) {
if [[ ${(M)BUFFER#$str} ]] {
BUFFER=$str${(qq)BUFFER#$str}
}
}
zle accept-line
}
zle -N quote-accept-line
# bind it to "Enter"
bindkey "^M" quote-accept-line

The special noglob command modifier can be used for this:
% calc.pl (1+1) / (2+2)
zsh: unknown file attribute: 1
% noglob calc.pl (1+1) / (2+2)
0.5
Typing noglob all the time will probably get boring fast, so you can create an alias for this in your zshrc:
% alias calc.pl='noglob calc.pl'
% calc.pl (1+1) / (2+2)
0.5
The difference between the "auto quoter" is that something like this:
% calc.pl 2' * 3
won't work, as the quotes are still interpreted. I have never run in to issues with this though, as I can't recall ever having used quotes in a calculation, but maybe your Perl script accepts some syntax for that.
Either way, it's a much simpler solution which is probably enough for your purpose.
Bonus: zsh comes with the zcalc module, which provides a calculator; I have no idea how this compares to your Perl script, but the way I have this set up is like this:
autoload -U zcalc # Get quick results for "zc 6 * 6", or just use "zc" to get zcalc
alias zc >/dev/null && unalias zc
zc() { if (( $# )); then zcalc -e ${(j: :)#}; else zcalc; fi }
alias zc='noglob zc'
By default zcalc will throw you in to a REPL unless you use -e, which I find a bit annoying; this way you can type zc (1.0+1) / (2+2) and get your results quickly.

[The question originally asked for a bash solution and was originally tagged bash.]
You're missing the point of the quotes or escapes. It's for the benefit of the shell, not calc.pl. (calc.pl doesn't even see them; it gets (1+1)/(2+2) when you execute the shell command calc.pl \(1+1\)/\(2+2\).)
The issue is that ( ... ) and spaces have special meaning to the shell, so escapes and/or quotes are used to change how the shell interprets them.
You can't do anything about that after the shell has already interpreted them incorrectly, so your request has no solution.
[While the above is still true in zsh, one can hook into zsh's command line editor as shown in the other answer. This allows the command to be edited before zsh sees it.]

Related

ZSH completion matching control : fuzzy match on one part of input

I'm writing a zsh completion for some program, and parts of it involves completing resource routes (/they/look/like/this).
I have a command mycmd that I can use to generate some completion candidates of resource routes, and provide completions for my program, using:
_multi_parts '/' "($(mycmd /some/resource/id))"
Now, I would like to implement a specific behavior to match resources that contain the last identifier in my query, not only those that start with it.
For example, $(mycmd "/resource/identifier/bc") gives completions like :
/resource/identifier/abc456/
/resource/identifier/123abcXYZ/
We get resource names whose identifier contains bc, which is the intended behavior.
Now here is the problem: zsh completion prevents these completions from showing up, because none of them matches "/resource/identifier/bc*", the default pattern for zsh.
I read the documentation for ZSH _multi_parts , compadd. It appears that using -M <pattern> described in compadd doc and available for _multi_parts could do the trick, by specifying a custom pattern for completion as described in ZSH Matching Control documentation. However this doc is lacking useful examples and is overall very obscure to me.
I spent hours of trials and errors to find the right pattern argument for _multi_parts -M <pattern> to achieve what I want with no success. Any hint on this would be much appreciated.
EDIT: actually, simply ignoring the last resource identifier bc would also work. I did not manage to do this either using -M.
Finally found out how to do this. The correct specification is:
_multi_parts -M 'l:|=*' '/' $completions
The pattern l:|=* allows to recognize matches which contain extra character after the string on the command line.
Looking at zsh completion log below (C-x ?), we find that it also uses patterns r:|/=* r:|=*.
In my understanding, using both r:|=* and l:|=* allows recognizing any completion candidate -- which is fine in my case, since filtering relevant suggestions is done with $mycmd.
Extract from ZSH completion log:
+_multi_parts:94> compadd -O tmp1 -M 'r:|/=* r:|=* l:|=*' - ENSG00000170615_SLC26A5_NT
+_multi_parts:96> [[ 1 -eq 0 ]]
+_multi_parts:99> [[ 1 -eq 1 ]]
+_multi_parts:106> [[ SLC = */* ]]
+_multi_parts:109> matches=( ENSG00000170615_SLC26A5_NT/ )
+_multi_parts:111> PREFIX=/run/echolocation_example/SLC
+_multi_parts:112> SUFFIX=''
+_multi_parts:114> [[ 0 -ne 0 ]]
+_multi_parts:115> zstyle -t :completion::complete:pelican:argument-2: expand suffix
+_multi_parts:119> (( 1 ))
+_multi_parts:120> compadd -p /run/echolocation_example/ -r / -S / -M 'r:|/=* r:|=* l:|=*' - ENSG00000170615_SLC26A5_NT

Avoid variable expansion in zsh

If I use the zsh shell and execute the following command I get
$zsh
$echo '$_GET["test"]'
preexec: bad math expression: operand expected at `"test"'
$echo '$_GET[]'
preexec: invalid subscript
In bash I get what I expect:
$bash
$echo '$_GET["test"]'
$_GET["test"]
I assume that zsh is trying to expand the $_GET variable. How can I avoid this? I always expected this to only happen within double quotes anyhow.
[update]
I found the following three lines in the .zshrc:
# Display last command interminal
echo -en "\e]2;Parrot Terminal\a"
preexec () { print -Pn "\e]0;$1 - Parrot Terminal\a" }
After commenting them out everything seems to work as expected.
What I understand is that preexec is executed after a command in the terminal has been submitted but before it is executed. The $1 is the command that one submitted.
I still do not understand the purpose of the two lines but is it because of the double quotes in the preexec print statement that the variables are expanded?
The combination of print -P together with the expansion of $1 is killing you. With this, you first get a "normal" expansion of $1, yielding something like "\e]0;echo '$_GET["test"]'...". Now -P causes print to do a prompt expansion on this string, which means that it has to expand $_GET["test"] as well. This causes the error.
I suggest to remove the -P, in particular since you don't have any characters in your string which would benefit from prompt expansion.

Process substitution =(list) in middle of argument

How can I use =(list)-style process substitution in the middle of an argument?
This works:
% echo =(echo)
/tmp/zshxxxxxx
So does this:
% echo =(echo):works
/tmp/zshxxxxxx:works
But this does not:
% echo broken:=(echo)
zsh: missing end of string
Notably, this also works:
% echo works:<(echo)
works:/proc/self/fd/11
The problem is =(list) can only stand at the beginning of arguments. Quoting from the ZSH manual:
The expression may be preceded or followed by other strings except
that, to prevent clashes with commonly occurring strings and patterns,
the last form [this is =(list)] must occur at the start of a command
argument, and the forms are only expanded when first parsing command
or assignment arguments.
I have a tool that accepts an argument of the form format:filename, and I need to use a real file, not a pipe, so I cannot use <(list). What is a reasonably simple and readable solution?
Use parameter expansion to "buffer" the process substitution.
% echo fixed:${:-=(echo)}
fixed:/tmp/zshxxxxxx
I have been trying to use the previous answer for a makefile, and it was not so trivial so here is my solution.
The initial problem is that with MinGW, the command line length is quite limited and it will get truncated in case of a very long object list, so I need to use the #file syntax for gcc, which allow to provide the arguments in a file.
SHELL := /bin/zsh
myprog.exe: very.o long.o list.o of.o obj.o files.o ...
gcc -o $# #$${:-=(<<< \"$^\")}
There is an alternate solution by using an anonymous function called immediatly :
myprog.exe: very.o long.o list.o of.o obj.o files.o ...
() { gcc -o $# #$$1 } =(<<< "$^")

Changing the global “path” from within a function?

My zshenv file has a bunch of lines like
if [[ -d "$HOME/bin" ]]; then
path=($HOME/bin $path)
fi
I thought I’d try to factor this pattern out into a function. I replaced it with
function prepend_to_path_if_exists() {
if [[ -d $1 ]]; then
path=($1 $path)
fi
}
prepend_to_path_if_exists("$HOME/bin")
but this gives the error
/Users/bdesham/.zshenv:8: missing end of string
where line 8 is the one where I’m calling prepend_to_path_if_exists. What exactly is causing this error, and how can I make this function work? I’m using zsh 5.0.5 on OS X 10.10.1.
You could call functions as with usual command executions like this (without ()):
prepend_to_path_if_exists "$HOME/bin"
It seems that zsh try to expand the glob prepend_to_path_if_exists(…) rather than to call the function.
TL;DR: Prepending emelemnts to $path would be accomplished by a little cryptic way:
(I'm not quite sure that the below form is preferable for anyone though.)
# `typeset -U` uniqify the elements of array.
# It could be good for $path.
typeset -U path
# prepending some paths unconditionally,
path[1,0]=(\
$HOME/bin \
$HOME/sbin \
)
# then filtering out unnecessary entries afterward.
path=(${^path}(-/N))
The $path[x,0]=… is prepending(splicing) element(s) to array taken from the below:
So that's the same as VAR[1,0]=(...) ? It doesn't really "look" very
much like prepend to me.
-- Greg Klanderman (http://www.zsh.org/mla/workers/2013/msg00031.html)
The ${^path}(-/N) expands the glob qualifires -/N on the each $path elements.
(Without ^ in the parameter expansion, the last elements of array will be evaluated, so it is mandatory in this case.)
The glob qualifires -/N means that "symbolic links and the files they point to"(-) the "directory"(/). And when it does not match anything do not raise errors (N).
In short, it would keep exsisting directories only for $path.

Commenting out a set of lines in a shell script

I was wondering if there is a way to comment out a set of lines in a shell script.
How could I do that? We can use /* */ in other programming languages.
This is most useful when I am converting/using/modifying another script
and I want to keep the original lines instead of deleting.
It seems a cumbersome job to find and prefix # for all the lines which are not used.
Lets say there are 100 lines in the script in consequent lines which are not to used.
I want to comment them all out in one go. Is that possible?
The most versatile and safe method is putting the comment into a void quoted
here-document, like this:
<<"COMMENT"
This long comment text includes ${parameter:=expansion}
`command substitution` and $((arithmetic++ + --expansion)).
COMMENT
Quoting the COMMENT delimiter above is necessary to prevent parameter
expansion, command substitution and arithmetic expansion, which would happen
otherwise, as Bash manual states and POSIX shell standard specifies.
In the case above, not quoting COMMENT would result in variable parameter
being assigned text expansion, if it was empty or unset, executing command
command substitution, incrementing variable arithmetic and decrementing
variable expansion.
Comparing other solutions to this:
Using if false; then comment text fi requires the comment text to be
syntactically correct Bash code whereas natural comments are often not, if
only for possible unbalanced apostrophes. The same goes for : || { comment text }
construct.
Putting comments into a single-quoted void command argument, as in :'comment
text', has the drawback of inability to include apostrophes. Double-quoted
arguments, as in :"comment text", are still subject to parameter expansion,
command substitution and arithmetic expansion, the same as unquoted
here-document contents and can lead to the side-effects described above.
Using scripts and editor facilities to automatically prefix each line in a
block with '#' has some merit, but doesn't exactly answer the question.
if false
then
...code...
fi
false always returns false so this will always skip the code.
You can also put multi-line comments using:
: '
comment1comment1
comment2comment2
comment3comment3
comment4comment4
'
As per the Bash Reference for Bourne Shell builtins
: (a colon)
: [arguments]
Do nothing beyond expanding arguments and performing redirections. The return status is zero.
Thanks to Ikram for pointing this out in the post Shell script put multiple line comment
You can use a 'here' document with no command to send it to.
#!/bin/bash
echo "Say Something"
<<COMMENT1
your comment 1
comment 2
blah
COMMENT1
echo "Do something else"
Wikipedia Reference
Text editors have an amazing feature called search and replace. You don't say what editor you use, but since shell scripts tend to be *nix, and I use VI, here's the command to comment lines 20 to 50 of some shell script:
:20,50s/^/#/
: || {
your code here
your code here
your code here
your code here
}
What if you just wrap your code into function?
So this:
cd ~/documents
mkdir test
echo "useless script" > about.txt
Becomes this:
CommentedOutBlock() {
cd ~/documents
mkdir test
echo "useless script" > about.txt
}
As per this site:
#!/bin/bash
foo=bar
: '
This is a test comment
Author foo bar
Released under GNU
'
echo "Init..."
# rest of script
Depending of the editor that you're using there are some shortcuts to comment a block of lines.
Another workaround would be to put your code in an "if (0)" conditional block ;)
This Perl one-liner comments out lines 1 to 3 of the file orig.sh inclusive (where the first line is numbered 0), and writes the commented version to cmt.sh.
perl -n -e '$s=1;$e=3; $_="#$_" if $i>=$s&&$i<=$e;print;$i++' orig.sh > cmt.sh
Obviously you can change the boundary numbers as required.
If you want to edit the file in place, it's even shorter:
perl -in -e '$s=1;$e=3; $_="#$_" if $i>=$s&&$i<=$e;print;$i++' orig.sh
Demo
$ cat orig.sh
a
b
c
d
e
f
$ perl -n -e '$s=1;$e=3; $_="#$_" if $i>=$s&&$i<=$e;print;$i++' orig.sh > cmt.sh
$ cat cmt.sh
a
#b
#c
#d
e
f

Resources