Avoid variable expansion in zsh - 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.

Related

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

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.]

adding a function to .zshrc changes the command

I added the following function to my .zshrc
function jptt(){
# Forwards port $1 into port $2 and listens to it
ssh -N -f -L localhost:$2:localhost:$1 remoteuser#remotehost
}
then I am running jptt 1 2
and get the following error:
Bad local forwarding specification localhost:2ocalhost:1
It is strange that I lose :l after the 2
the function is working when as I tried to replace the command with a simple line and it worked. I also run the ssh command separately and it works well.
The expression $x:l applies the lower-casing modifier to your x variable. The following example illustrates this:
pax> x=ABC
pax> echo $x:lnnn
abcnnn
pax> echo ${x}:lnnn
ABC:lnnn
The first section gives you the lower-case variant, and therefore the modifier is not considered part of your output string. The second section shows how you can prevent this variable expansion by using braces to ensure the :l is not treated as a modifier. In your specific case, that would be done with the line:
ssh -N -f -L localhost:${2}:localhost:${1} remoteuser#remotehost
It's actually a good idea to get into the habit of bracing parameter names as much as possible since there are other cases where this might adversely affect you.
Use ${1} and ${2} . Zsh supports : csh string modificators and :l has special meaning (to lowercase variable in front of it) and that's why it is consumed from $1:localhost.

Zsh read output of command into array splitting on newline

I have a command that outputs a bunch of stuff e.g. running mycmd will give:
foobar
derp derp
like so
etc
Some of these lines will have spaces in them.
How do I read these into an array in zsh such that ${arr[1]} gives foobar, ${arr[2]} gives derp derp etc.
I have tried something like but it seems to split the array on chars not newlines.
IFS=$'\n' read -d '' -r arr <<< "$(mycmd)"
i.e. ${arr[1]} gives f when it should give foobar
Okay its actually very simple:
IFS=$'\n' arr=($(mycmd))
I'm not sure exactly why the read usage in the original question didn't work. It's possibly related to mixing <<< and $(). Or maybe the user just had a messed up shell session. Or maybe it was a bug in an older version of Zsh.
In any case, it has nothing to do with the behavior of the read builtin, and the original proposal was very close to correct. The only problem was using <<< $(...) instead of a plain pipe, which should just be a stylistic goof (rather than an error).
The following works perfectly fine in Zsh 5.8.1 and 5.9:
function mycmd {
print foobar
print derp derp
print like so
print etc
}
typeset -a lines
mycmd | IFS=$'\n' read -r -d '' -A lines
echo ${(F)lines}
You should see:
foobar
derp derp
like so
etc
I prefer this style, instead of ( $(...) ). Not requiring a subshell is useful in many cases, and the quoting/escaping situation is a lot simpler.
Note that -d '' is required to prevent read from terminating at the first newline.
You can wrap this up in a function easily:
function read-lines {
if (( $# != 1 )); then
print -u2 'Exactly 1 argument is required.'
return 2
fi
local array="${1:-}"
read -r -d '' "$array"
}

Zsh: How to force file completion everywhere following a set of characters?

I'm trying to figure out how to get file completion to work at any word position on the command line after a set of characters. As listed in a shell these characters would be [ =+-\'\"()] (the whitespace is tab and space). Zsh will do this, but only after the backtick character, '`', or $(. mksh does this except not after the characters [+-].
By word position on the command line, I'm talking about each set of characters you type out which are delimited by space and a few other characters. For example,
print Hello World,
has three words at positions 1-3. At position 1, when you're first typing stuff in, completion is pretty much perfect. File completion works after all of the characters I mentioned. After the first word, the completion system gets more limited since it's smart. This is useful for commands, but limiting where you can do file completion isn't particularly helpful.
Here are some examples of where file completion doesn't work for me but should in my opinion:
: ${a:=/...}
echo "${a:-/...}"
make LDFLAGS+='-nostdlib /.../crt1.o /.../crti.o ...'
env a=/... b=/... ...
I've looked at rebinding '^I' (tab) with the handful of different completion widgets Zsh comes with and changing my zstyle ':completion:*' lines. Nothing has worked so far to change this default Zsh behaviour. I'm thinking I need to create a completion function that I can add to the end of my zstyle ':completion:*' completer ... line as a last resort completion.
In the completion function, one route would be to cut out the current word I want to complete, complete it, and then re-insert the completion back into the line if that's possible. It could also be more like _precommand which shifts the second word to the first word so that normal command completion works.
I was able to modify _precommand so that you can complete commands at any word position. This is the new file, I named it _commando and added its directory to my fpath:
#compdef -
# precommands is made local in _main_complete
precommands+=($words[1,$(( CURRENT -1 ))])
shift words
CURRENT=1
_normal
To use it I added it to the end of my ':completion:*' completer ... line in my zshrc so it works with every program in $path. Basically whatever word you're typing in is considered the first word, so command completion works at every word position on the command line.
I'm trying to figure out a way to do the same thing for file completion, but it looks a little more complicated at first glace. I'm not really sure where to go with this, so I'm looking to get some help on this.
I took a closer look at some of Zsh's builtin functions and noticed a few that have special completion behaviour. They belong to the typeset group, which has a function _typeset in the default fpath. I only needed to extract a few lines for what I wanted to do. These are the lines I extracted:
...
elif [[ "$PREFIX" = *\=* ]]; then
compstate[parameter]="${PREFIX%%\=*}"
compset -P 1 '*='
_value
...
These few lines allow typeset completion after each slash in a command like this:
typeset file1=/... file2=~/... file3=/...
I extrapolated from this to create the following function. You can modify it to put in your fpath. I just defined it in my zshrc like this:
_reallyforcefilecompletion() {
local prefix_char
for prefix_char in ' ' $'\t' '=' '+' '-' "'" '"' ')' ':'; do
if [[ "$PREFIX" = *${prefix_char}* ]]; then
if [[ "$PREFIX" = *[\'\"]* ]]; then
compset -q -P "*${prefix_char}"
else
compset -P "*${prefix_char}"
fi
_value
break
fi
done
}
You can use this by adding it to a zstyle line like this:
zstyle ':completion:*' completer _complete _reallyforcefilecompletion
This way, it's only used as a last resort so that smarter completions can try before it. Here's a little explanation of the function starting with the few variables and the command involved:
prefix_char: This gets set to each prefix character we want to complete after. For example, env a=123 has the prefix character =.
PREFIX: Initially this will be set to the part of the current word from the beginning of the word up to the position of the cursor; it may be altered to give a common prefix for all matches.
IPREFIX (not shown in code): compset moves string matches from PREFIX to IPREFIX so that the rest of PREFIX can be completed.
compset: This command simplifies modification of the special parameters, while its return status allows tests on them to be carried out.
_value: Not really sure about this one. The documentation states it plays some sort of role in completion.
Documentation for the completion system
The function: In the second line, we declare prefix_char local to avoid variable pollution. In line three, we start a for loop selecting each prefix_char we want to complete after. In the next if block, we check if the variable PREFIX ends with one of the prefix_chars we want to complete after and if PREFIX contains any quotes. Since PREFIX contains quotes, we use compset -q to basically allow quotes to be ignored so we can complete in them. compset -P strips PREFIX and moves it to IPREFIX, basically so it gets ignored and completion can work.
The next elif statement is for a PREFIX ending with prefix_char but not containing quotes, so we only use compset -P. I added the return 0 to break the loop. A more correct way to make this function would be in a case statement, but we're not using the compset return value, so this works. You don't see anything about file completion besides _value. For the most part we just told the system to ignore part of the word.
Basically this is what the function does. We have a line that looks like:
env TERM=linux PATH=/<---cursor here
The cursor is at the end of that slash. This function allows PREFIX, which is PATH=, to be ignored, so we have:
env TERM=linux /<---cursor here
You can complete a file there with PATH= removed. The function doesn't actually remove the PATH= though, it just recategorizes it as something to ignore.
With this function, you can now complete in all of the examples I listed in the question and a lot more.
One last thing to mention, adding this force-list line in your zshrc cripples this function somehow. It still works but seems to choke. This new force-list function is way better anyway.
zstyle ':completion:*' force-list always
EDIT: There were a couple lines I forgot to copy into the function. Probably should have checked before posting. I think it's good now.

zsh - iterate over files matched using parameter in the script

I have this code executed fine and correctly in the zsh shell:
for f in ./A*.html; do; echo $f; done
output
./Aliasing.html
./Alternate-Forms-For-Complex-Commands.html
./Alternative-Completion.html
./Arguments.html
./Arithmetic-Evaluation.html
./Arithmetic-Expansion.html
./Array-Parameters.html
./Author.html
./Availability.html
However when I use this code, but sending a matching string (./A*.html) as a parameter in the zsh function, it will display only the first file
script:
displayy() {
for f in $1; do; echo $f; done
}
command:
%displayy ./A*.html
output
./Aliasing.html
I would rather expect the same files are printed out as when executing for loop in shell (first example). Any ideas what I'm doing wrong? Grazie
The problem with displayy ./A*.html command is that * is expanded by zsh before it is passed to the dispayy function. So in fact your command looks like this:
$ displayy ./Aliasing.html ./Alternate-Forms-For-Complex-Commands.html ...
Then in displayy you print just first argument: ./Aliasing.html.
The easiest way to solve this issue is to change one char (1=>#) in you displayy definition:
displayy() {
for f in "$#"; do; echo "$f"; done
}
This way iteration of loop goes through all display arguments. Additionally I recommend to put double quotes around variables as a good habit, even if in this example there are no whitespaces in file names.

Resources