How to Run a Shell Command in a zsh-theme? - zsh

So, I have Oh My Zsh up and running, and I'm creating my own new zsh-theme. In it, I wish to grab the external IP address from https://api.myip.com - and I'm using curl & grep to grab it. Works fine when I enter it at the command prompt, but when embedded in my zsh-theme file it gives me an error:
zsh: no matches found: ((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5]).){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])
(23) Failed writing body
Jacobs-MacBook-Pro-2.local jacobjackson ttys002 0 [ ] 10/29/20 18:32:46 PM
Here is my zsh-theme:
PROMPT='%F{white}%M %n %y %j $(curl -s https://api.myip.com | grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])') %F{green}%2c%F{blue} [%f '
RPROMPT='$(git_prompt_info) %F{blue}] %F{green}%W %* %F{yellow}%D{%p}%f'
ZSH_THEME_GIT_PROMPT_PREFIX="%F{yellow}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%f"
ZSH_THEME_GIT_PROMPT_DIRTY=" %F{red}*%f"
ZSH_THEME_GIT_PROMPT_CLEAN=""
And here is the command sequence that grabs the IP address:
curl -s https://api.myip.com | grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])'

Try this:
# Function name that's compatible with
# http://zsh.sourceforge.net/Doc/Release/User-Contributions.html#Prompt-Themes
# in case you ever want to build a full prompt theme.
# -s to prevent curl from outputting a progress bar.
# Use a service that simply outputs your IP, so you
# don't have to parse anything.
prompt_jacobjackson_precmd() {
psvar[1]=$( curl -s ifconfig.co )
}
# precmd hooks ru just before each new prompt.
autoload -Uz add-zsh-hook
add-zsh-hook precmd prompt_jacobjackson_precmd
# %1v inserts the 1st element of the psvar array. See
# http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html#Conditional-Substrings-in-Prompts
PS1='%1v > '

I decided to use some of Marlon Richert's ideas as well a few from the zsh-theme 'bureau.' :)
get_space () {
local STR=$1$2
local zero='%([BSUbfksu]|([FB]|){*})'
local LENGTH=${#${(S%%)STR//$~zero/}}
local SPACES=""
(( LENGTH = ${COLUMNS} - $LENGTH - 1))
for i in {0..$LENGTH}
do
SPACES="$SPACES "
done
echo $SPACES
}
_1LEFT="%F{white}$(curl -s https://api.myip.com | jq .ip -r) %F{green}\$(dirs -c; dirs)"
_1RIGHT="%F{yellow}%j jobs %F{cyan}\$(~/systemstatus.sh)"
actionjackson_precmd () {
_1SPACES=`get_space $_1LEFT $_1RIGHT`
#print
print -rP "$_1LEFT$_1SPACES$_1RIGHT"
}
setopt prompt_subst
PROMPT='%F{yellow}%n%F{white}#%F{green}%M $_LIBERTY%f '
RPROMPT='$(actionjackson_git_prompt) %F{green}%W %* %F{yellow}%D{%p}%f'
autoload -U add-zsh-hook
add-zsh-hook precmd actionjackson_precmd

Related

zsh: print time next to command on execute

I want to configure zsh to append the time each command started next to the line command was executed on. For example:
# before I press ENTER
$ ./script
# after I press enter
$ ./script [15:55:58]
Running script...
I came up with the following config (which also colors the timestamp yellow):
preexec () {
TIME=`date +"[%H:%M:%S] "`
echo -e "$1 %{$fg[yellow]%}\033[1A\033[1C${TIME}$reset_color"
}
But it breaks and prints { and % characters on basic commands such as cat and echo. It also breaks on password prompts (macOS terminal). For example with echo:
$ echo "hello" [15:55:58]
hello"hello" %{%}
How can I fix this config?
Thank you.
You inspired me and based on your script I wrote mine. I have tested this on zsh 5.4.smth.
preexec () {
local TIME=`date +"[%H:%M:%S] "`
local zero='%([BSUbfksu]|([FK]|){*})'
local PROMPTLEN=${#${(S%%)PROMPT//$~zero/}}
echo "\033[1A\033[$(($(echo -n $1 | wc -m)+$PROMPTLEN))C $fg[blue]${TIME}$reset_color"
}
In your ~/.zshrc file, put:
function preexec() {
timer=${timer:-$SECONDS}
}
function precmd() {
if [ $timer ]; then
timer_show=$(($SECONDS - $timer))
export RPROMPT="%F{cyan}${timer_show}s %F{$black%}"
unset timer
fi
}
And that should give you something like this:

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

Unexpected no such file error in zsh function

I have defined this function in zsh, but it cannot work on my computer.
function m() {
local path=/Users/james/Music/cloud_music_link
local cmd="ls $path | sed -n $1p"
echo `$cmd`
# afplay `$cmd`
}
It always said:
m:3: no such file or directory: ls /Users/james/Music/cloud_music_link | sed -n 10p
and when I copy the ls /Users/james/Music/cloud_music_link | sed -n 10p and run it in zsh, everything is ok, why would this happen?
And the folder cloud_music_link is a soft link, I am not sure if this matters.
You want to pass the file (or subdirectory) at a given position inside of your given directory first to echo, and then to aplay? Easily done, in a way compatible with both bash and zsh:
m() {
local num=${1:-1}
local dir=/Users/james/Music/cloud_music_link
local -a files=( "$dir"/* )
local file=${files[$(( num - 1 ))]}
echo "$file"
afplay "$file" &
}
See:
Why you shouldn't parse the output of ls
I'm trying to put a command in a variable, but complex cases always fail!
Finally I have figured it out, it is because the name of the variable path, I changed it to p and everything works OK. The name path will conflict with zsh.
This code works:
function m() {
local p=/Users/james/Music/cloud_music_link/
song="$(ls $p | sed -n $1p)"
echo $song
killall afplay
afplay "$p$song"&
}

Output shell script to log file

I have a scheduled unix script that I want to log the output of. I am unable to edit the cron file due to the user interface restrictions, and I am unable to add >> logfile to the command. Is there something I can add within the script itself to send the output to a log?
{
printf poo
#Do not change
PRINTF=/usr/bin/printf
MSMTP=/usr/local/bin/msmtp
MSMTPCONF=/var/etc/msmtp.conf
#Can be changed
FROM="nas4free#usinfosec.com"
TO="dpatino#usinfosec.com"
MDIR="CaseData"
SUBJECT="$MDIR Backup Report"
} > /mnt/support/logs/$SUBJECT.log
#BODY="$(cat /mnt/support/logs/test.log)"
#$PRINTF "From:$FROM\nTo:$TO\nSubject:$SUBJECT\n\n$BODY" | $MSMTP --file=$MSMTPCONF -t
Try
#!/bin/bash
exec > /tmp/myLog.log 2>&1
set -x
The log shows:
+ echo 'Hello World!'
Hello World!
One way is to wrap your script in braces and redirect the output as shown below:
#!/bin/bash
{
# script contents here
echo running script
} > logfile
Append following line in starting of your script
log_file_path="/tmp/output.log"
log() { while IFS='' read -r line; do echo "$line" >> "$log_file_path"; done; };
exec > >(tee >(log)) 2>&1
Your script with modification
PRINTF=/usr/bin/printf
MSMTP=/usr/local/bin/msmtp
MSMTPCONF=/var/etc/msmtp.conf
FROM="nas4free#usinfosec.com"
TO="dpatino#usinfosec.com"
MDIR="CaseData"
SUBJECT="$MDIR Backup Report"
{
printf poo
} > /mnt/support/logs/$SUBJECT.log
#BODY="$(cat /mnt/support/logs/test.log)"
#$PRINTF "From:$FROM\nTo:$TO\nSubject:$SUBJECT\n\n$BODY" | $MSMTP --file=$MSMTPCONF -t

How to custom display prompt in KornShell to show hostname and current directory?

I am using KornShell (ksh) on Solaris and currently my PS1 env var is:
PS1="${HOSTNAME}:\${PWD} \$ "
And the prompt displays: hostname:/full/path/to/current/directory $
However, I would like it to display: hostname:directory $
In other words, how can I display just the hostname and the name of the current directory, i.e. tmp or ~ or public_html etc etc?
From reading the ksh man page you want
PS1="${HOSTNAME}:\${PWD##*/} \$ "
Tested on default ksh on SunOS 5.8
Okay, a little old and a little late, but this is what I use in Kornshell:
PS1='$(print -n "`logname`#`hostname`:";if [[ "${PWD#$HOME}" != "$PWD" ]] then; print -n "~${PWD#$HOME}"; else; print -n "$PWD";fi;print "\n$ ")'
This makes a prompt that's equivalent to PS1="\u#\h:\w\n$ " in BASH.
For example:
qazwart#mybook:~
$ cd bin
qazwart#mybook:~/bin
$ cd /usr/local/bin
qazwart#mybook:/usr/local/bin
$
I like a two line prompt because I sometimes have very long directory names, and they can take up a lot of the command line. If you want a one line prompt, just leave off the "\n" on the last print statement:
PS1='$(print -n "`logname`#`hostname`:";if [[ "${PWD#$HOME}" != "$PWD" ]] then; print -n "~${PWD#$HOME}"; else; print -n "$PWD";fi;print "$ ")'
That's equivalent to PS1="\u#\h:\w$ " in BASH:
qazwart#mybook:~$ cd bin
qazwart#mybook:~/bin$ cd /usr/local/bin
qazwart#mybook:/usr/local/bin$
It's not quite as easy as setting up a BASH prompt, but you get the idea. Simply write a script for PS1 and Kornshell will execute it.
For Solaris and other Older Versions of Kornshell
I found that the above does not work on Solaris. Instead, you'll have to do it the real hackish way...
In your .profile, make sure that ENV="$HOME/.kshrc"; export ENV
is set. This is probably setup correctly for you.
In your .kshrc file, you'll be doing two things
You'll be defining a function called _cd. This function will change to the directory specified, and then set your PS1 variable based upon your pwd.
You'll be setting up an alias cd to run the _cd function.
This is the relevant part of the .kshrc file:
function _cd {
logname=$(logname) #Or however you can set the login name
machine=$(hostname) #Or however you set your host name
$directory = $1
$pattern = $2 #For "cd foo bar"
#
# First cd to the directory
# We can use "\cd" to evoke the non-alias original version of the cd command
#
if [ "$pattern" ]
then
\cd "$directory" "$pattern"
elif [ "$directory" ]
then
\cd "$directory"
else
\cd
fi
#
# Now that we're in the directory, let's set our prompt
#
$directory=$PWD
shortname=${directory#$HOME} #Possible Subdir of $HOME
if [ "$shortName" = "" ] #This is the HOME directory
then
prompt="~$logname" # Or maybe just "~". Your choice
elif [ "$shortName" = "$directory" ] #Not a subdir of $HOME
then
prompt="$directory"
else
prompt="~$shortName"
fi
PS1="$logname#$hostname:$prompt$ " #You put it together the way you like
}
alias cd="_cd"
This will set your prompt as the equivelent BASH PS1="\u#\h:\w$ ". It isn't pretty, but it works.
ENV=~/.kshrc, and then in your .kshrc:
function _cd {
\cd "$#"
PS1=$(
print -n "$LOGNAME#$HOSTNAME:"
if [[ "${PWD#$HOME}" != "$PWD" ]]; then
print -n "~${PWD#$HOME}"
else
print -n "$PWD"
fi
print "$ "
)
}
alias cd=_cd
cd "$PWD"
Brad
HOST=`hostname`
PS1='$(print -n "[${USER}#${HOST%%.*} ";[[ "$HOME" == "$PWD" ]] && print -n "~" ||([[ "${PWD##*/}" == "" ]] && print -n "/" || print -n "${PWD##*/}");print "]$")'
PS1=`id -un`#`hostname -s`:'$PWD'$
and...
if you work between two shells for most of your effort [ksh and bourne sh]
and desire a directory tracking display on your command line
then PWD can be substituted easily in ksh
and if you use /usr/xpg4/bin/sh for your sh SHELL, it will work there as well
Try this:
PS1="\H:\W"
More information on: How to: Change / Setup bash custom prompt, I know you said ksh, but I am pretty sure it will work.

Resources