Process substitution =(list) in middle of argument - zsh

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 } =(<<< "$^")

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

how to echo literal variable value with zsh

I have a simple shell function to convert a *nix style path to Windows style (I happen to be using Windows Subsystem for Linux).
# convert "/mnt/c/Users/josh" to "C:\Users\josh"
function winpath(){
enteredPath=$1
newPath="${enteredPath/\/mnt\/c/C:}" # replace /mount/c/ with C:
newPath="${newPath//\//\\}" # replace / with \
echo $newPath
}
The desired behavior is:
$ winpath /mnt/c/Users/josh
C:\Users\josh
This works correctly in bash, but in zsh, echo seems to do some extra interpolation of the $newPath value. It behaves like this:
$ winpath /mnt/c/Users/josh
C:sers\josh
What character sequence is echo interpolating and why is it remove the \U? Most importantly, how do I return the literal value?
I've tried digging through the zsh documentation, but it's a jungle. Thanks in advance!
zsh processes certain escape sequences that bash does not by default. \U introduces 4-byte Unicode codepoint, but since the following 8 characters are not a valid hexadecimal number, no character is substituted.
I would recommend using printf, as its behavior is much more predictable from shell to shell.
printf '%s\n' "$newPath"
The problem is that you are using the internal command echo, instead of the external one. If you would write
command echo $newPath
you would get the expected output. command forces zsh to look up the command word according to the current PATH, ignoring internal commands, aliases or functions of the same name.

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.

Make zsh complete arguments from a file

zsh is great but its completion system is very diverse. And the documentation lacks good examples. Is there a template for completing for a specific application. The completion would get its match data from a file, separated by newlines?
I tried modifying an older example of mine that takes match data "live":
~ % cat .zsh/completers/_jazzup
#compdef jazz_up
_arguments "2: :(`mpc lsplaylists|sed -e 's# #\\\\ #g'`)"
I could supply cat my_file there instead of mpc invocation and so on but would there be a more elegant way to do this simple task? And that completion there is placement-specific: can you provide an example where zsh would attempt to complete at any point after the program name is recognized?
The match data will have whitespaces and so on, the completion should escape the WS. Example of that:
Foo bar
Barbaric
Get it (42)
Now if that completion would be configured for a command Say, we should get this kind of behaviour out of zsh:
$ Say Fo<TAB>
$ Say Foo\ bar
$ Say Ge<TAB>
$ Say Get\ it\ \(42\)
Simple completion needs are better addressed with _describe, it pairs an array holding completion options and a description for them (you can use multiple array/description pairs, check the manual).
(_arguments is great but too complex.)
[...]
First create a file
echo "foo\nbar\nbaz\nwith spac e s\noh:noes\noh\:yes" >! ~/simple-complete
Then create a file _simple somewhere in your $fpath:
#compdef simple
# you may wish to modify the expansion options here
# PS: 'f' is the flag making one entry per line
cmds=( ${(uf)"$(< ~/simple-complete)"} )
# main advantage here is that it is easy to understand, see alternative below
_describe 'a description of the completion options' cmds
# this is the equivalent _arguments command... too complex for what it does
## _arguments '*:foo:(${cmds})'
then
function simple() { echo $* }
autoload _simple # do not forget BEFORE the next cmd!
compdef _simple simple # binds the completion function to a command
simple [TAB]
it works. Just make sure the completion file _simple is placed somewhere in your fpath.
Notice that : in the option list is supposed to be used for separating an option from their (individual) description (oh:noes). So that won't work with _describe unless you quote it (oh\:yes). The commented out _arguments example will not use the : as a separator.
Without changing anything further in .zshrc (I already have autoload -Uz compinit
compinit) I added the following as /usr/local/share/zsh/site-functions/_drush
#compdef drush
_arguments "1: :($(/usr/local/bin/aliases-drush.php))"
Where /usr/local/bin/aliases-drush.php just prints a list of strings, each string being a potential first argument for the command drush. You could use ($(< filename)) to complete from filename.
I based this on https://unix.stackexchange.com/a/458850/9452 -- it's surprising how simple this is at the end of the day.

need help in unix for iterating a for loop

The bellow line code is not working.it is not iterating through the directory.
input={20132802,20132802}
for i in $(ls -1 /home/$input/*s.log)
do
...
done
but when providing the input in the loop working fine.
for i in $(ls -1 /home/{20132802,20132802}/*s.log)
do
...
done
please help.
Brace expansion cannot be used in variables because it is performed before any other expansions.
From man bash:
Brace expansion is performed before any other expansions, and any
characters special to other expansions are preserved in the result. It
is strictly textual. Bash does not apply any syntactic interpretation
to the context of the expansion or the text between the braces. To
avoid conflicts with parameter expansion, the string ‘${’ is not
considered eligible for brace expansion.
However, you can use eval to overcome this limitation:
input={20132802,20132802}
for i in $(eval ls -1 /home/$input/*s.log)
do
...
done
Obligatory reading: eval is evil
Try writing ${input} instead of $input since i is the index of the for loop and I guess $i is used when you write $input ('$i'nput).

Resources