Coloring directory name in ksh - ksh

In my current situation, it is not unusual for me to have several UNIX computers I connect to, as several different users depending on the situation, and to traverse through various directories on the machines doing things. I use ksh through the entire thing.
I was fiddling with my prompt recently, and I was able to get it to change some colors depending on my current username and current server. However, what I would also want is for it to change colors based on my current directory. For example, if I were in directory "foo", the prompt should be yellow, but if I were in directory "bar", the prompt would be magenta. In both cases, subdirectories should also count, so a simple substring check should be enough.
The problem I ran into, however, is that when I run my .profile script, it properly colors the directory--but it no longer dynamically updates whenever I switch to another directory--and I'm not sure how before I did all the branching, I was able to get it to print my current working directory correctly even after I switched directories.
I did some googling, and find information for bash, but ksh seems to be largely ignored. As I cannot figure out how to do this on my own, I must bring it to the Stack Overflow community, to add it to future knowledge. Thus, with my long-winded explanation, the "quick version" of my question is as follows:
In ksh, how can I set up my prompt to display the current working directory and color the text based on where the current working directory is? Is it even possible?

Why not using zsh? It is based on ksh, and it is much more powerful. In zsh you can write chpwd function that is implicitly called every time you change directory. In this function you can check your current directory and set PS1 to whatever you want.
Alternatively (even in ksh) you can create an alias for cd command:
change_my_ps() {
PS1=...
}
better_cd() {
builtin cd "$#"
change_my_ps
}
alias cd=better_cd
Something like this. I'm not sure it is proper, I don't remember ksh syntax.

I was able to dig up a semi-solution here:
http://books.google.com/books?id=QYu_v2R6fIQC&pg=PA71&lpg=PA71&dq=korn+dynamic+prompt&source=bl&ots=yMEZiWrGyU&sig=8KBbs12Mtk3eGNSZQiLVmFYZVFY&hl=en&ei=2HX4Sej0K6LWMP2NxakP&sa=X&oi=book_result&ct=result&resnum=2#PPA72,M1
Though enacting it is still difficult.

To display the current directory in ksh, put this in your .profile file:
export PS1="\$PWD "
That will dynamically update when you change directory without mucking around with functions.

Since nobody actually answered the part about the color, here's how I'd do it. I can't color the text, so I've also included a screenshot of my terminal.
See Wikipedia's page on ANSI escape codes for the full list of color codes and print --man and printf --man for the details of printing escape sequences in ksh.
(Note that ksh93v, currently in beta, will contain a prompt language based (I think) on bash's, which will make this sort of thing somewhat easier, though I think you'll probably still need to use something like this function to do complex conditional prompts like this one.)
> cat ~/scripts/prompt
function prompt
{
set -eu
typeset c=
case $PWD in
*/foo) c=3;; # yellow
*/bar) c=5;; # magenta
esac
print "\E[3${c}m$PWD\E[m > "
}
PS1='`prompt`'
> . ~/scripts/prompt
/Users/adavies > cd foo
/Users/adavies/foo > cd ../bar
/Users/adavies/bar >

I use this:
function chdir
{
cd "$#"
CWDH=${PWD%/*}
PS1="($_time)$hname:${CWDH##*/}/${PWD##*/} ->"
export PS1
}
alias cd=chdir
chdir .
Ignore the time and hname, but the rest should work for you. Changing colors is going to be terminal dependent. You need to know the escape codes for each color for the terminal you will be using. If you know you only ever use an xterm, it will be easier.

Related

Detect prompt with xterm.js

TLDR: I want know how to detect from the output of a shell (e.g. zsh, bash) the location of the prompts (e.g. user#machine /etc % ).
Details
I have made a working shell frontend in the browser based on xtermjs. It is now equivalent feature-wise to e.g. the default macOS terminal application with zsh, bash and powershell. In a nutshell, it works by executing a shell process (e.g. zsh) as the child of a parent process that pipes the input/output from/to the browser via web sockets.
I want now to step up and implement a "collapse" functionality that hides the output of the selected commands in the history (like Visual Studio Code does now).
To this end, I need to detect the location of the prompts from the terminal output: the collapse function would then hide the characters between two consecutive prompts.
I know I can use the approaches below:
detect the prompt with a regular expression (I would need to parse the PS1 variable)
inject some special character sequence before and after the prompt (e.g. in variable PS1)
But both do not seem very robust, and may not work with some specific command interpreter. I could not find yet the location where this functionality is implemented in the source code of Visual Studio Code.
My question is: is there a robust way to achieve this functionality for at least zsh, bash and powershell (it is fine if it is specific to xterm.js)
Edit 1
This SO question is related: ANSI escape sequence for collapsing/folding text (maybe hierarchically)
It links to this interesting thread: https://github.com/PerBothner/DomTerm/issues/54
It appears that DomTerm uses escapes sequences at folding points (my solution 2).
Yet I don't see how to inject them into the terminal, besides hacking the PS1 env var.
Edit 2
While parsing iTerm's documentation I found out that it takes advantage of the hooks provided by the shell (e.g. for zsh) in order to print some special escape sequence at various locations, including before showing the prompt.
For example, in zsh, I can print string "šŸ®" before each prompt be executing precmd() { echo 'šŸ®' }. Then when I execute e.g. ls I get
$> ls
[...]
šŸ®
$>
There is a more extensive explanation of the various available hooks for various shells here.
It looks like PowerShell uses a very different system though.

How to set custom variables for directories in shell?

so I'm new to the world of Linux. Due to my new internship however, I have to work a lot with it and thus also get a hang of using the terminal appropriately.
Is there a way to set path-variables for faster navigation through directories? Similar to using '~' as an abbreviation for '/home/usr/'.
So far, I have tried to use:
```
name#torch:~$ export var=/home/usr/where/i/want/to/go
name#torch:~$ cd $var
name#torch:~/where/i/want/to/go$ ...
```
This option works only temporarily in a single shell but is not adapted when closing down the terminal or starting another one next to it. Is there a way to define more general path-variables?
Best wishes,
Hauke

Add Path to OSX to El Capitan

I am trying to learn UNIX.
I am using a book called ā€œWicked Cool Shell Scriptsā€.
I am told that .bash_profile contains my login for bash and that I can add paths to it so that commands I enter in Terminal will find the scripts I am writing.
The contents of my current bash_profile is:
export PATH=~/bin:$PATH
When I type echo $PATH I get:
/usr/local/opt/php#7.0/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.3/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin
I would like to add a path so that - as the book suggests - I can write scripts and refer to them directly from the command line, instead of having to constantly navigate to that directory to run them.
I have a file with a shebang. It runs fine when I type its name and am in the same directory. I have moved that file to the folder scripts, which is located under crg/Users/ (ie: Users/crg/scripts)
According to this book, I can now alter my $PATH to include that directory, so that when I type that filename, the program will run.
I cannot do this successfully.
I donā€™t know why.
After every edit, I quit terminal and reopen it, to ensure it is using the newly edited bash_profile.
As per the books instructions on page 5, I have tried entering this in my bash_profile:
export PATH=ā€/Users/crg/scripts/:$PATHā€
I save my bash_profile, quit Terminal, reopen it and type echo $Path
This is the result:
ā€/Users/crg/scripts/:??
This is not right. In fact, it's wildly wrong. And it does not allow me to run scripts from the folder indicated. It also seems to completely overwrite whatever was in the bash_profile before this, so I cannot - after doing this 'simple edit' suggested by a 'professional' - run a php -version command from the Terminal.
I am at a complete loss as to why this is happening.
Why is there a quotation mark at the beginning of this line (but not at the end)?
What's with the colon and the 2 question marks at the end of this line?
How do I add/append a path to my bash_profile?
More questions:
When I try and solve this on my own using ā€œthe Internetā€, I discover many interesting versions of this ā€˜simpleā€™ process: Hereā€™s one suggested by a ā€˜professionalā€™:
export PATH="${PATH}:/path/to/program/inside/package"
This is very different from what the book says...
Hereā€™s another version of ā€˜how to do itā€™ by ā€˜a professionalā€™:
export PATH=$PATH:/usr/local/sbin/modemZapp
Notice that this one doesnā€™t even have quotes. In both, the PATH variable comes before the actual path.
Why are there so many 'versions' of how to perform this simple task?
Can someone please tell me how to add a path to my .bash_profile?
UPDATE: I have followed the advice here, (add it to etc/paths) but this does not work either.
I get the exact same thing when I type echo $PATH in a new Terminal:
/usr/local/opt/php#7.0/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.3/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr:/usr/local/share/npm/bin:/Users/fhb/scripts:/opt/X11/bin
I can't help but note a comment on that last page: "I have been through at least a dozen different methods for adding directories to $PATH. Why are there so many and why do so few of them work?"
To answer your first question if you look closely your quotes are ā€ instead of ". I'm guessing you edited either your bash_profile or this post in a rich text editor instead of a plain text one. I would recommend notepad for Windows or nano for *nix if you are writing code. To fix this issue, replace the ā€ with ".
To answer your second question, bash is quite forgiving and will allow you to set a string variable in multiple different ways, even without quotes. However you can run into issues when a string contains whitespace, for example: /Users/lilHenry/my scripts.
The "${PATH}" syntax is just another way to declare a string. It has the benefit that it allows you to interpolate variables into a string like so:
prefix="foo"
echo "${prefix}bar"
This will output foobar, whereas echo "$prefixbar" will not output anything as the variable prefixbar has not been set. I would suggest sticking with the export PATH="/Users/me/bin:$PATH" syntax.

Converting a history command into a shell script

This is sort of one of those things that I figured a lot of people would use a lot, but I can't seem to find any people who have written about this sort of thing.
I find that a lot of times I do a lot of iteration on a command-line one-liner and when I end up using it a lot, or anticipate wanting to use it in the future, or when it becomes cumbersome to work with in one line, it generally is a good idea to turn the one-liner into a shell script and stick it somewhere reasonable and easily accessible like ~/bin.
It's obviously too cumbersome to use any sort of roundabout method involving a text editor to get this done, and it's possible to simply do it on the shell, for instance in zsh typing
echo "#!/usr/bin/env sh" > ~/bin/command_from_history_number_523.sh && echo !523 >> ~/bin/command_from_history_number_523.sh
followed by pressing Tab to inject the !523rd command and somehow shoehorning it into an acceptable string to be saved.
This is particularly cumbersome and has at minimum three problems:
Does not work in bash as it does not complete the !523
Requires some manual inspection and string escapement
Requires too much typing such as the script name must be entered twice
So it looks like I need to do some meta shell scripting here.
I think a good solution would function under both bash and zsh, and it should probably work by taking two arguments, an integer for the history command number and a name for the shell script to poop out in a hardcoded directory which contains that one command. Furthermore, under bash, it appears that multi-line commands are treated as separate commands, but I'm willing to assume that we only care about one-liners here and I only use zsh anyway at this point.
The stumbling block here is that i think I'll still be running shell scripts through bash even when using zsh, so it won't likely then be able to parse zsh's history files. I may need to make this into two separate programs then.
Update: I agree with #Floris 's comment that direct use of the commands like !! would be helpful though I am not sure how to make this work. Suppose I have the usage be
mkscript command_number_24 !24
this is inadequate because mkscript will be receiving the expanded out contents of the 24th command. if the 24th command contains any file globs or somesuch they will have been expanded already. This is bad, and I basically want the contents of the history file, i.e. the raw command string. I guess this can be worked around by manually implementing those shortcuts in here. Or just screw it and just take an integer argument.
function mkscript() {
echo '#!/bin/bash' > ~/bin/$2
history -p '!'$1 >> ~/bin/$2
}
Only tested in Bash.
Update from OP: In zsh I can accomplish this with fc -l $2 $2

ZSH auto-complete screws up command name

When I start doing tab auto-complete of a command, it keeps what I initally typed next to it and the command becomes unreadable. In the example below, I typed 'git che' and hit tab. Once I select 'checkout' the command prompt becomes 'git che git checkout'. The command still works and in my history it stores 'git checkout'. But its pretty annoying visually. Is there anyway to change this behavior. I tried this in 2 different terminal emulators, so I can confirm its ZSH and not the emulator. Thanks
Screenshot
EDIT:
echo $ZSH_VERSION
4.3.10
It doesnt seem to happen with zsh -f. Though its hard to tell since the only autocomplete that works is directories. I'm using 'oh-my-zsh' with this custom theme:
autoload -U add-zsh-hook
add-zsh-hook chpwd do_ls_on_chdir
function do_ls_on_chdir() {
ls;
}
function dirStack(){
OUT='';
NUM=1;
for X in $(dirs | cut -d ' ' -f2-10); do
OUT="$OUT$1%B$NUM:%b$1$X ";
(( NUM=NUM+1 ))
done
echo $OUT;
}
ZSH_THEME_GIT_PROMPT_ADDED=""
ZSH_THEME_GIT_PROMPT_MODIFIED=""
ZSH_THEME_GIT_PROMPT_DELETED=""
ZSH_THEME_GIT_PROMPT_RENAMED=""
ZSH_THEME_GIT_PROMPT_UNMERGED=""
ZSH_THEME_GIT_PROMPT_UNTRACKED=""
ZSH_THEME_GIT_PROMPT_AHEAD="%{$fg_bold[yellow]%}ā†‘"
ZSH_THEME_GIT_PROMPT_PREFIX=""
ZSH_THEME_GIT_PROMPT_SUFFIX=""
ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[red]%}āœ—"
ZSH_THEME_GIT_PROMPT_CLEAN=" %{$fg_bold[green]%}āœ”"
local user_color='blue'
local back="${BG[237]}"
test $UID -eq 0 && user_color='red'
PROMPT='$(dirStack $back)
$back%B%!%b$back %{$fg_bold[$user_color]%}%~%{$reset_color%}'\
'$back $(git_prompt_status)%{$reset_color%}'\
'$back%{$fg_bold[magenta]%}$(git_prompt_info)%{$reset_color%}'\
'$back$(git_prompt_ahead)$reset_color'\
'$back%(!.#.>)$reset_color '
PROMPT2='%{$fg[red]%}%_ %{$reset_color%}'
PROMPT3='%{$fg[red]%}... %{$reset_color%}'
RPROMPT='%(?..%{$fg_bold[red]%}exit %?%{$reset_color%})'\
' %{$FG[186]%}(%D %*)%{$reset_color%}'
SOLUTION:
NOTE: stackoverflow wont let me answer my own question since I asked it within the past 8 hours. I dont feel like waiting.
So I figured it out. It turns out I wasnt properly escaping the ANSI color codes (I think). Everywhere I had $reset_color in my PROMPT variable, I changed that to %{$reset_color%} and it fixed it.
So I figured it out. It turns out I wasnt properly escaping the ANSI color codes (I think). Everywhere I had $reset_color in my PROMPT variable, I changed that to %{$reset_color%} and it fixed it.
I only discovered this link tonight, after messing with my prompt - I'd always wondered why the ZSH prompt examples seemed so needlessly complex.
When you set up the colors in your zsh prompt, you should escape things with %{ [...] %}, so that 'the shell knows there is no output from these sequences and the cursor hasn't moved'.
If you don't escape this, the shell believes that your cursor has moved (even though it hasn't). This leads to messed up prompts, and rather annoying visual effects when you use tab-completion etc.
Here's some screenshots without escaping the reset-color prompt sequence (PR_NO_COLOUR="%{$terminfo[sgr0]%}", in my prompt settings). As we can see, the cursor starts in the wrong place:
It should be here:
And after trying tab-completion without escaping, the prompt is all confused about where the text is, and where the cursor should be placed:
(The cursor should be placed at the end of the directory, not halfway through).
So the prompt sequences look like this:
PS1="%{$fg[red]%}%n%{$reset_color%}#%{$fg[blue]%}%m %{$fg[yellow]%}%~ %{$reset_color%}%% "
because everything has to be escaped nicely inside %{...%} pairs.

Resources