Are prompt and PROMPT tied in zsh? - zsh

$ echo ${(t)prompt}
scalar-special
$ echo ${(t)PROMPT}
scalar-special
Zsh shows that they are special, but does not show that they are tied like:
$ echo ${(t)manpath}
array-tied-special
$ echo ${(t)MANPATH}
scalar-tied-export-special

From the man page:
PROMPT <S> <Z>
Same as PS1 ...
prompt <S> <Z>
Same as PS1.
This means that if you modify one of PROMPT, prompt or PS1, and then look at one of the other two variables, you will see that your modifications are there as well.
PROMPT=foo
echo $PS1 # outputs foo
echo $prompt # outputs foo

Related

Show multiline aliases in zsh with line breaks as in bash

When printing all available aliases on stdout via command alias a multiline alias in bash will printed with the line breaks but it does not in zsh.
Example with the identical multiline alias definition for bash .bash_aliases and zsh .zsh_aliases:
alias hello='
echo "Hello"
echo "beautiful"
echo "world"'
When the alias hello is executed the result is the same on both shells.
But when comparing the printout of the definiton via alias hello on stdout...
in bash the output is:
alias hello='
echo "Hello"
echo "beautiful"
echo "world"'
whereas in zsh the output looks like this:
hello=$'\n echo "Hello"\n echo "beautiful"\n echo "world"'
Why is the \n in zsh not printed as new line and the tabulator \t not respected like in bash`s stdout?
I tried several escaping but without success.
You can use the print built-in to display the text with newlines:
print "$(alias)"
e.g.
> print "$(alias hello)"
hello=$'
echo "Hello"
echo "beautiful"
echo "world"'
It's usually preferable to use a function for anything with multiple lines rather than an alias. With a function you have fewer worries about quoting and a generally easier-to-follow syntax:
hello2() {
print Hello
print beautiful
print world
}

How can I capture the work of an async subshell or coproc into a variable of the parent?

Summary
I have a script that gets a list of files, and needs to perform some validation on them which might take a few seconds.
In the interim, I have some user input I'd like to collect so there potentially is no visible delay while waiting for the validation to finish.
I would like to build an array variable with the list of valid files to do the final piece of work on those files.
I've tried various invocations of coproc or subshells, but I haven't managed to find any method that will work other than having the subshell write to a temp file managed by the parent and then have the parent read the contents of that temp file into the variable.
This feels to me like I need to learn how to use zsh better, so I am posing this question to the fine people monitoring this tag.
Here are two small example scripts that represent the parent as well as a proxy for the stand-alone executable that is invoked by the subshell to do the validation work.
Parent script
#!/usr/bin/env zsh
# Generate test files
echo "one" > a.txt
echo "two" >> a.txt
echo "3" > b.txt
echo "four" >> b.txt
echo "five" > c.txt
echo "6" >> c.txt
echo "seven" > d.txt
echo "eight" >> d.txt
echo "9" > e.txt
echo "10" >> e.txt
# This list is built by doing a `print -l **/*.{txt,csv}(Od)` command.
filesToValidate=($(print -l [abcde].txt))
echo "Files to validate are: $filesToValidate"
# I need to use this list later in the script, but have some other stuff that can be done
# in the interim while waiting for it to complete.
validFiles=()
# I would like to run this piece of code asynchronously
for file in $filesToValidate
do
./has_numerals.sh $file >/dev/null 2>&1 && validFiles+=("$file")
done
# end async code
read -q "REPLY?Have you thought about your answer long enough?"
echo .
# If the validation is done in a subshell or coproc or something,
# at this point, wait for the work to be done (if it isn't already).
wait
for file in $validFiles
do
echo "Contents of valid file $file:"
cat $file
done
Stand-alone validation executable example ( has_numerals.sh )
#!/usr/bin/env zsh
echo "processing $1"
sleep 1
grep -l '[0-9]' $1
I want to learn how to do that if it is possible.
While it is potentially possible, it is not advisable. The mktemp tool is designed for this purpose. I will show an example below.
Zsh documentation doesn't touch on the subject, but bash makes it clear it is not possible in their documentation:
Changes made to the subshell environment cannot affect the shell's execution environment.
The only way of running an asynchronous subshell and retrieving the output is to store the information in a file, write to a pipe file you later read from the parent script, or some far more elaborate method of inter-process communication.
How best to use mktemp?
Here is a version of your script reworded to use mktemp.
#!/usr/bin/env zsh
# Generate test files
echo "one" > a.txt
echo "two" >> a.txt
echo "3" > b.txt
echo "four" >> b.txt
echo "five" > c.txt
echo "6" >> c.txt
echo "seven" > d.txt
echo "eight" >> d.txt
echo "9" > e.txt
echo "10" >> e.txt
echo "11" > f.csv
echo "12" >> f.csv
# Generate the list safely from find.
filesToValidate=$(find . \( -name \*.txt -o -name \*.csv \) -print)
echo "Files to validate are: $filesToValidate"
# Make a temp directory. This should work on Linux and macOS.
TMPFILE=$(mktemp 2>/dev/null || mktemp -t 'validfiles')
( \
echo -n "$filesToValidate" | \
xargs -n 1 ./has_numerals.sh "$TMPFILE" \
) &
# Pass the files to xargs with only one per line (-n 1),
# the temp file, and the file to validate.
TRAPINT() {
# Trap for ^C
rm -f "${TMPFILE}"
}
TRAPQUIT() {
# Trap for ^\
rm -f "${TMPFILE}"
}
TRAPTERM() {
# Trap for receiving kill -TERM
rm -f "${TMPFILE}"
}
TRAPEXIT() {
# Trap for any exit
rm -f "${TMPFILE}"
}
# end async code
read -q "REPLY?Have you thought about your answer long enough?"
echo .
# Wait for subshell, if required.
wait
# Loop on newline separated (#f) data from TMPFILE.
for file in "${(#f)"$(<$TMPFILE)"}"
{
cat "$file"
}
Sample output:
% ./do.sh
Files to validate are: ./f.csv
./c.txt
./b.txt
./a.txt
./e.txt
./d.txt
Have you thought about your answer long enough?processing ./f.csv
processing ./c.txt
processing ./b.txt
processing ./a.txt
processing ./e.txt
processing ./d.txt
y.
11
12
five
6
3
four
9
10

Enter a command for a user

I have written a ZSH function whose output is a command line which runs a program I need the user to be able to interact with.
At the moment I just echo the command line and instruct the user to copy-paste it so that they have the necessary access to its pipes, however is there a way I can just have the function finish by entering the command for the user as if they had copied and pasted it themselves?
I have looked into using zle but that seems to require a key binding, whereas I just want the user to be able to run: myzshfunction arg1 and the ultimate result to be their terminal attached to the program launched as a result of some processing of their arg1.
$ myzshfunction arg2*2
Run this command! foobar baz4
$ foobar baz4
...
The function looks something like this:
myzshfunction() {
if [[ $# = 0 ]]
then
echo "usage: myzshfunction 1.2.3.4"
return
fi
local creds=`curl "https://xxx/$1/latest" | jq -r 'x'`
local cred_arr=("${(#s|/|)creds}")
local pwd_pipe=$(mktemp -u)
mkfifo $pwd_pipe
exec 3<>$pwd_pipe
rm $pwd_pipe
echo $cred_arr[2] >&3
echo "Run this: sshpass -d3 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$cred_arr[1]#$1"
exec 3>&-
}
TIA
Use print -z to add text to the buffer. From the documentation:
-z Push the arguments onto the editing buffer stack, separated by spaces.
Calling foo, defined below, will result in hi being placed on the command line as if the user had typed it. For example,
% foo () { print -z hi; }
% foo
% hi
The best solution I could come up with was to use zle - the Zsh Line Editor.
This lets you update the command the user is currently editing, which to me feels a bit hacky. I would prefer a solution that lets you call a function, hit return, and then cleanly run a function with STDIO attached to your terminal as if you had run the resulting command line.
Perhaps you could emulate this by bindkey'ing the return key and doing some sort of decision/routing from there to see if the user wants to call myfunc. For now, my solution requires the Esc+i sequence is entered after typing a target for $host.
zle-myfunc() {
apikey=$(keychain-environment-variable api-key)
if [ $? -ne 0 ]; then
echo "Add your api-key to the keychain: "
BUFFER='security add-generic-password -U -a ${USER} -D "environment variable" -s "api-key" -w'
zle accept-line
return 1
fi
local host=$BUFFER
zle kill-buffer
local creds=`curl ..." | jq -r ...`
if [ -z creds ]; then
echo "Couldn't get creds, check your network"
return 1
fi
local creds_arr=("${(#s|/|)creds}")
local pwd_pipe=$(mktemp -u)
mkfifo $pwd_pipe
exec 3<>$pwd_pipe
# anonymise the pipe
rm $pwd_pipe
echo "$creds_arr[2]" >&3
BUFFER="sshpass -d3 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $creds_arr[1]#$host"
zle accept-line
# exec 3>&-
}
zle -N zle-myfunc
bindkey '\ei' zle-myfunc

How do I convert this zsh function to fish shell?

I have this function which works great in zsh, but I want to convert it to fish shell and I can't get it working.
function ogf () {
echo "Cloning, your editor will open when clone has completed..."
source <(TARGET_DIRECTORY=~/students EDITOR=$EDITOR clone_git_file -ts "$1")
}
First of all, since fish's syntax differs from zsh, you also have to change the output of clone_git_file to source it.
For example, if clone_git_file is something like:
#!/bin/bash
echo "FOO=$TARGET_DIRECTORY"
echo "BAR=$2"
you have to change it to fish syntax.
#!/bin/bash
echo "set -gx FOO $TARGET_DIRECTORY"
echo "set -gx BAR $2"
Now here's the ogf() function, and sample code for fish:
function ogf
echo "Cloning, your editor will open when clone has completed..."
source (env TARGET_DIRECTORY=~/students EDITOR=$EDITOR clone_git_file -ts $argv[1] | psub)
end
ogf MY_ARGUMENT
echo "FOO is $FOO"
echo "BAR is $BAR"
Running this code with fish, the output is:
FOO is /home/MY_USER/students
BAR is MY_ARGUMENT

How to quote strings in file names in zsh (passing back to other scripts)

I have a script that has a string in a file name like so:
filename_with_spaces="a file with spaces"
echo test > "$filename_with_spaces"
test_expect_success "test1: filename with spaces" "
run cat \"$filename_with_spaces\"
run grep test \"$filename_with_spaces\"
"
test_expect_success is defined as:
test_expect_success () {
echo "expecting success: $1"
eval "$2"
}
and run is defined as:
#!/bin/zsh
# make nice filename removing special characters, replace space with _
filename=`echo $# | tr ' ' _ | tr -cd 'a-zA-Z0-9_.'`.run
echo "#!/bin/zsh" > $filename
print "$#" >> $filename
chmod +x $filename
./$filename
But when I run the toplevel script test_expect_success... I get cat_a_file_with_spaces.run with:
#!/bin/zsh
cat a file with spaces
The problem is the quotes around a file with spaces in cat_a_file_with_spaces.run is missing. How do you get Z shell to keep the correct quoting?
Thanks
Try
run cat ${(q)filename_with_spaces}
. It is what (q) modifier was written for. Same for run script:
echo -E ${(q)#} >> $filename
. And it is not bash, you don't need to put quotes around variables: unless you specify some option (don't remember which exactly)
command $var
always passes exactly one argument to command no matter what is in $var. To ensure that some zsh option will not alter the behavior, put
emulate -L zsh
at the top of every script.
Note that initial variant (run cat \"$filename_with_spaces\") is not a correct quoting: filename may contain any character except NULL and / used for separating directories. ${(q)} takes care about it.
Update: I would have written test_expect_success function in the following fashion:
function test_expect_success()
{
emulate -L zsh
echo "Expecting success: $1" ; shift
$#
}
Usage:
test_expect_success "Message" run cat $filename_with_spaces

Resources