Changing path to current working directory in zsh prompt - zsh

I'm trying to modify an existing zsh prompt to work with zsh 5.0 and 4.3 because those the versions the systems that I use. How would I make a zsh-script be aware of the current working directory instead of the directory that the file is in?
For context,
This is a function in the script that checks if we're currently in a git directory and adds to the prompt if we are:
# Git status.
# Collect indicators, git branch and pring string.
spaceship_git_status() {
[[ $SPACESHIP_GIT_SHOW == false ]] && return
# Check if the current directory is in a Git repository.
command git rev-parse --is-inside-work-tree &>/dev/null || return
# Check if the current directory is in .git before running git checks.
if [[ "$(git rev-parse --is-inside-git-dir 2> /dev/null)" == 'false' ]]; then
# Ensure the index is up to date.
git update-index --really-refresh -q &>/dev/null
# String of indicators
local indicators=''
indicators+="$(spaceship_git_uncomitted)"
indicators+="$(spaceship_git_unstaged)"
indicators+="$(spaceship_git_untracked)"
indicators+="$(spaceship_git_stashed)"
indicators+="$(spaceship_git_unpushed_unpulled)"
[ -n "${indicators}" ] && indicators=" [${indicators}]";
echo -n " %Bon%b "
echo -n "%{$fg_bold[magenta]%}"
echo -n "$(git_current_branch)"
echo -n "%{$reset_color%}"
echo -n "%{$fg_bold[red]%}"
echo -n "$indicators"
echo -n "%{$reset_color%}"
fi
}
However, based on my debugging, it appears that the function always believes that it is in the directory from which the script was sourced. In other words, as I change directory, the script continues to reference the directory where the script is located.
The spaceship_git_status function is called here:
# Build prompt line
spaceship_build_prompt() {
spaceship_host
spaceship_current_dir
spaceship_git_status
spaceship_nvm_status
spaceship_ruby_version
spaceship_venv_status
}
And this is the PROMPT environment variable is:
# Compose PROMPT
PROMPT=''
[[ $SPACESHIP_PROMPT_ADD_NEWLINE == true ]] && PROMPT="$PROMPT$NEWLINE"
PROMPT="$PROMPT $(spaceship_build_prompt) "
[[ $SPACESHIP_PROMPT_SEPARATE_LINE == true ]] && PROMPT="$PROMPT$NEWLINE"
PROMPT="$PROMPT $(spaceship_return_status) "
I think this is an issue with zsh versions < 5.2 because the prompt renders fine on my other computer with 5.2.
Full code: https://github.com/denysdovhan/spaceship-zsh-theme/blob/master/spaceship.zsh

Related

Running custom zsh function for tmux status bar not displaying output

I'm wrote a function called test_status that I am trying to incorporate in my tmux status bar. To give some background, my tests will output to a file called .guard_result with either success or failure and the test_status function reads from that file and echoes a 💚 if my tests are passing and a ❤️ if they are failing.
The good news is running test_status works just fine, I'm just having trouble getting it to work with tmux. What am I missing here?
# ~/.oh-my-zsh/custom/aliases.zsh
function test_status {
if [ ! -f "./.guard_result" ]; then
echo "?"
return 1
fi
result="$(cat ./.guard_result)"
if [[ $result == *"success"* ]]
then
echo "💚";
elif [[ $result == *"fail"* ]]
then
echo "❤️";
fi
}
This function works... Here is Tmux configuration (which doesn't show result):
# ~/.tmux.conf
set -g status-right "#(test_status) #[fg=colour245]%d %b %Y #[fg=white]:: #[fg=colour245]%l:%M %p"
I know I must be missing something simple... Thanks for your help!
tmux passes shell commands to /bin/sh not zsh. And even if tmux would use zsh, the function would not be available in that context as ~/.zshrc, which loads oh-my-zsh, is only read for interactive shells.
In order to get the the output of test_status into tmux, I would suggest to put the function into a zsh script and call that.
You can either source ~/.oh-my-zsh/custom/aliases.zsh from within the script and then call test_status:
#!/usr/bin/zsh
# ^ make sure this reflects the path to zsh (`type zsh`)
source ~/.oh-my-zsh/custom/aliases.zsh
test_status
Or you can just put the entire function into the script, so as to not clutter alias.zsh:
#!/usr/bin/zsh
function test_status {
if [ ! -f "./.guard_result" ]; then
echo "?"
return 1
fi
result="$(cat ./.guard_result)"
if [[ $result == *"success"* ]]
then
echo "💚";
elif [[ $result == *"fail"* ]]
then
echo "❤️";
fi
}
Safe the script somewhere (e.g. /path/to/test_status.zsh), make it executable (chmod a+x /path/to/test_status.zsh) and call it by path in the tmux configuration.

ZSH setopt PROMPT_SUBST not working

I'm trying to get customize my zsh prompt and want to evaluation a function with git commands every time my prompt is generated. I'm using setopt PROMPT_SUBST, but it doesn't seem to be working. This is my zshrc:
setopt PROMPT_SUBST
autoload -U colors && colors # Enable colors
# Show Git branch/tag, or name-rev if on detached head
parse_git_branch() {
echo "PARSING GIT BRANCH"
(git symbolic-ref -q HEAD || git name-rev --name-only --no-undefined --always HEAD) 2> /dev/null
}
prompt() {
echo -n "%/"
echo "$(git status)"
}
PS1="$(prompt)"
And this is my output of setopt:
interactive
login
monitor
nonomatch
promptsubst
shinstdin
zle
You need to delay calling prompt until the prompt is displayed; do that by using single quotes:
PS1='$(prompt)'
A better idea, though, is to define a function that sets PS1, then add that function to the precmd_functions array so that it is executed prior to displaying each prompt.
prompt () {
PS1="%/$(git status)"
}
precmd_functions+=(prompt)

How to tell script to look only into a specific folder

I'm trying to make a recycle bin for UNIX, so I have two scripts. 1 to delete the file and move it to the bin, the other script to restore the file back to its original location.
my restore script only works if the person gives the path to the deleted file.
ex: sh restore ~/trashbin/filename
How do I hardcode into my script so that I don't need to give the path to the deleted file it should already know to look in the trashbin for the file. My restore script works only when someone calls in the path to the file.
#!/bin/bash
rlink=$(readlink -e "$1")
rname=$(basename "$rlink")
function restoreFile() {
rlink=$(readlink -e "$1")
rname=$(basename "$rlink")
rorgpath=$(grep "$rname" ~/.restore.info | cut -d":" -f2)
rdirect=$(dirname "$rorgpath")
#echo $orgpath
if [ ! -d "$rdirect" ]
then
mkdir -p $rdirect
#echo $var
mv $rlink $rorgpath
else
mv $rlink $rorgpath
fi
}
if [ -z "$1" ]
then
echo "Error no filename provided."
exit 1
elif [ ! -f "$1" ]
then
echo "Error file does not exist."
exit 1
elif [ -f "$rorgpath" ]
then
echo "File already exists in original path."
read -p "Would you like to overwrite it? (y/n)" ovr
if [[ $ovr = y || $ovr = Y || $ovr = yes ]]
then
echo "Restoring File and overwriting."
restoreFile $1
grep -v "$rname" ~/.restore.info > ~/.restorebackup.info
mv ~/.restorebackup.info ~/.restore.info
fi
else
echo "Restoring file into original path."
restoreFile $1
grep -v "$rname" ~/.restore.info > ~/.restorebackup.info
mv ~/.restorebackup.info ~/.restore.info
fi
When you "remove" the file from the file-system to your trash-bin, move it so that its path is remembered. Example: removing file /home/user/file.txt should mean moving this file to ~/.trash/home/user/file.txt. That way, you'll be able to restore files to the original location, and you'll have auto-complete work, since you can do: sh restore ~/.trash/<TAB><TAB>

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.

Find out if a command exists on POSIX system

I want to be able to tell if a command exists on any POSIX system from a shell script.
On Linux, I can do the following:
if which <command>; then
...snip...
fi
However, Solaris and MacOS which do not give an exit failure code when the command does not exist, they just print an error message to STDOUT.
Also, I recently discovered that the which command itself is not POSIX (see http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html)
Any ideas?
command -v is a POSIX specified command that does what which does.
It is defined to to return >0 when the command is not found or an error occurs.
You could read the stdout/stderr of "which" into a variable or an array (using backticks) rather than checking for an exit code.
If the system does not have a "which" or "where" command, you could also grab the contents of the $PATH variable, then loop over all the directories and search for the given executable. That's essentially what which does (although it might use some caching/optimization of $PATH results).
One which utility is available as shell script in the Git repository of debianutils package of Debian Linux. The script seems to be POSIX compatible and you could use it, if you take into account copyright and license. Note that there have been some controversy whether or not and how the which utility should be deprecated; (at time of writing) current version in Git shows deprecation message whereas an earlier version added later removed -s option to enable silent operation.
command -v as such is problematic as it may output a shell function name, an alias definition, a keyword, a builtin or a non-executable file path. On the other hand some path(s) output by which would not be executed by shell if you run the respective argument as such or as an argument for command. As an alternative for using the which script, a POSIX shell function using command -v could be something like
#!/bin/sh
# Argument $1 should be the basename of the command to be searched for.
# Outputs the absolute path of the command with that name found first in
# a directory listed in PATH environment variable, if the name is not
# shadowed by a special built-in utility, a regular built-in utility not
# associated with a PATH search, or a shell reserved word; otherwise
# outputs nothing and returns 1. If this function prints something for
# an argument, it is the path of the same executable as what 'command'
# would execute for the same argument.
executable() {
if cmd=$(unset -f -- "$1"; command -v -- "$1") \
&& [ -z "${cmd##/*}" ] && [ -x "$cmd" ]; then
printf '%s\n' "$cmd"
else
return 1
fi
}
Disclaimer: Note that the script using command -v above does not find an executable whose name equals a name of a special built-in utility, a regular built-in utility not associated with a PATH search, or a shell reserved word. It might not find an executable either in case if there is non-executable file and executable file available in PATH search.
A function_command_exists for checking if a command exists:
#!/bin/sh
set -eu
function_command_exists() {
local command="$1"
local IFS=":" # paths are delimited with a colon in $PATH
# iterate over dir paths having executables
for search_dir in $PATH
do
# seek only in dir (excluding subdirs) for a file with an exact (case sensitive) name
found_path="$(find "$search_dir" -maxdepth 1 -name "$command" -type f 2>/dev/null)"
# (positive) if a path to a command was found and it was executable
test -n "$found_path" && \
test -x "$found_path" && \
return 0
done
# (negative) if a path to an executable of a command was not found
return 1
}
# example usage
echo "example 1";
command="ls"
if function_command_exists "$command"; then
echo "Command: "\'$command\'" exists"
else
echo "Command: "\'$command\'" does not exist"
fi
command="notpresent"
if function_command_exists "$command"; then
echo "Command: "\'$command\'" exists"
else
echo "Command: "\'$command\'" does not exist"
fi
echo "example 2";
command="ls"
function_command_exists "$command" && echo "Command: "\'$command\'" exists"
command="notpresent"
function_command_exists "$command" && echo "Command: "\'$command\'" does not exist"
echo "End of the script"
output:
example 1
Command: 'ls' exists
Command: 'notpresent' does not exist
example 2
Command: 'ls' exists
End of the script
Note that even the set -eu that turns -e option for the script was used the script was executed to the last line "End of the script"
There is no Command: 'notpresent' does not exist in the example 2 because of the && operator so the execution of echo "Command: "\'$command\'" does not exist" is skipped but the execution of the script continues till the end.
Note that the function_command_exists does not check if you have a right to execute the command. This needs to be done separately.
Solution with command -v <command-to-check>
#!/bin/sh
set -eu;
# check if a command exists (Yes)
command -v echo > /dev/null && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
echo "<handle not found 1>"
fi
# check if a command exists (No)
command -v command-that-does-not-exists > /dev/null && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
echo "<handle not found 2>"
fi
produces:
<handle not found 2>
because echo was found at the first example.
Solution with running a command and handling errors including command not found.
#!/bin/sh
set -eu;
# check if a command exists (No)
command -v command-that-does-not-exist > /dev/null && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
echo "<handle not found 2>"
fi
# run command and handle errors (no problem expected, echo exist)
echo "three" && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
echo "<handle not found 3>"
elif [ "${status}" -ne 0 ]; then
echo "<handle other error 3>"
fi
# run command and handle errors (<handle not found 4> expected)
command-that-does-not-exist && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
echo "<handle not found 4>"
elif [ "${status}" -ne 0 ]; then
echo "<handle other error 4>"
fi
# run command and handle errors (command exists but <handle other error 5> expected)
ls non-existing-path && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
echo "<handle not found 5>"
elif [ "${status}" -ne 0 ]; then
echo "<handle other error 5>"
fi
produces:
<handle not found 2>
three
./function_command_exists.sh: 34: ./function_command_exists.sh: command-that-does-not-exist: not found
<handle not found 4>
ls: cannot access 'non-existing-path': No such file or directory
<handle other error 5>
The following works in both bash and zsh and avoids both functions and aliases.
It returns 1 if the binary is not found.
bin_path () {
if [[ -n ${ZSH_VERSION:-} ]]; then
builtin whence -cp "$1" 2> /dev/null
else
builtin type -P "$1"
fi
}

Resources