Book Image

Learning Shell Scripting with Zsh

By : Gaston Festari
Book Image

Learning Shell Scripting with Zsh

By: Gaston Festari

Overview of this book

Table of Contents (13 chapters)

The shell prompt


Give anyone enough time with a shell and, inevitably, the question of "how do I add colors to it?" is bound to come up. Luckily though, zsh boasts a truckload of configuration options and escape sequences that will let you do just that and even more. In this section, we'll delve into the nuts and bolts of options at your disposal to customize the prompt.

The prompt command

Zsh comes with a wide array of predefined prompt configurations that can be used as building blocks for something that more adequately meets your needs. Among other things, the utility prompt allows you to select your preferred theme. On a default installation, the various themes and user contributions are located under <zshFolder>/Functions/Prompts (or <zshFolder>/functions in OS X) and follow the naming scheme prompt_<theme>_setup. To have a look at what's included in the stock package, just type the following command:

$ prompt –p

And you should see a list of all the available prompt themes included with zsh. You can use the -p option together with a theme name to take a closer look at any of the themes:

$ prompt -p 

In order to use the prompt function, you will need to set up the promptinit module on your shell. The easiest way to do this is to add it to your .zshrc file. Take a look at the section First run if you haven't done so yet.

Note

You can refer to the PROMPT THEMES section under the zshcontrib(1) manpage in order to get more in-depth information regarding prompts on zsh. Just type man zshcontrib in your terminal to get started.

You can test drive any theme you like, applying it temporarily to your current shell by typing:

$ prompt <theme_name>

Some themes, such as adam1, can even accept some extra configuration parameters like the following:

$ prompt adam1 red yellow magenta # sets the 'adam1' theme

By default, zsh won't be too fond of comments typed in the command line. Luckily, you can alter this behavior by setting the following option in your .zshrc file:

setopt INTERACTIVE_COMMENTS  # allow inline comments like this one

In the previous snippet, we are passing a list of options to the theme, namely the colors red, yellow, and magenta. You can get a more thorough description of what's allowed for each prompt theme by calling the built-in help on any given theme:

$ prompt -h <theme_name>

Try this on your favorite themes and see what else can be tweaked out of them.

Once you have found a combination that suits you, you can go ahead and commit to those changes. Just open your .zshrc file with your editor and add the following line:

autoload -U promptinit
promptinit
prompt adam1 red yellow magenta

We took our previous preferences file and sparkled some color in the default prompt adam1. So, how about we tweak it to make it feel more like home?

Note

If you have invested a fair amount of time on customizing your prompt in your previous shell, it can be quite a headache trying to figure out the different rules set, so it can be ported to zsh. Luckily, zsh provides a series of tools for making the switch a more or less smooth experience. Located under <zshFolder>/Misc, you can use the bash2zshprompt or c2z scripts to migrate your Bash or csh preferences respectively. Note, however, that some distributions might be missing this, in which case you should head straight to the official repo and get your hands on a local copy. See the Compiling from source section for more information on how to get the zsh source code.

Customizing the prompt

Zsh boasts five different prompts you can tweak, each with its specific purpose. Although you probably won't have to worry about dealing with them in most usage scenarios, it is, nevertheless, important that we get to know their role. For a more detailed description of each of them, I suggest you take a look at man zshmisc.

Zsh likes to refer to its main prompt variable as $PS1 or its alias, $PROMPT (also $prompt). Rest assured though, both (actually the three of them, that is) are the same beast and are treated equally by zsh. Then there's $RPS1 that prints a prompt at the right-hand side of the screen. Unlike other prompts though, it automatically disappears whenever line width is needed.

$PS2 gets displayed whenever the shell is waiting for more input, such as at the start of some unfinished syntactic structure or when you add inline comments to the command line. $PS3 is used for making choices within a select loop control mechanism. Last but not the least, $PS4 really comes in handy for debugging scripts.

Overall, these are the set of tools we will be working with, extending their functionality beyond the basics with a nifty set of tools known as escape sequences.

Tip

You can use the source command to reload your zsh configuration files at any time. Just save your changes and call the following command:

$ source file_path/file_name

Remember to use double quotes if your file path includes spaces.

$ source "random folder/.zshenv"

Using escape sequences

Escape sequences are a set of predefined information shortcuts that can be added to zsh's prompt settings. They can show information such as the name of the machine to which you are logged on, the current date and time of the system, and even the current working directory. Most escape sequences are defined with a modulo or percent (%) operator, and some of them even take optional parameters to extend their functionality further.

For the magic to happen, however, we first need to add a new setting to our preferences file. Open .zshrc and add the following line:

setopt PROMPT_SUBST

By doing this, we're enabling the PROMPT_SUBST option. This will make zsh treat $PROMPT just as if it were a vanilla shell variable, and it will be checked against for command substitution, parameter and arithmetic expansion.

Next, we'll go through many of the available escape sequences and their meanings. Keep in mind that this is by no means a complete list of all the available options; as such, you can always refer to the zshmisc(1) manpage—particularly, the section titled Prompt Expansion—should you need a more comprehensive listing of the available options.

Shell state options

The following options serve as indicators for some aspects of the current state of the shell:

  • %#: This displays # if the shell is running with elevated privileges and displays % otherwise

  • %?: This shows the exit status code of the last command executed

  • %h or %!: This shows the current history event number

  • %L: This displays the current value of the $SHLVL variable

  • %j: This prints the number of jobs being executed

Login information options

The following options display more useful information about the host and machine on which the shell is currently running:

  • %M: This shows the machine's hostname.

  • %m: Same as the previous. Hostname is printed up to the first dot (.) separator. It takes an optional integer after % for the number of components to be displayed.

  • %n: This will have the same effect as printing environment variable $USERNAME.

Directory options

The following options provide information regarding the path of the current working directory ($PWD) and filesystem directories:

  • %d or %/: This shows the current directory. Works just as printing the $PWD environment variable.

  • %~: Same as the previous, but if the current directory is $HOME, ~ is displayed instead.

  • %c or %.: This lists the amount of directories trailing $PWD. It takes an integer as the parameter after %. Thus, %2c would show the two preceding directories to $PWD.

  • %C: Same as the previous, but directory names are not replaced with any symbols.

Date and time options

The following options provide miscellaneous date and time information:

  • %D: This prints the current system date in the yy-mm-dd format.

  • %W: Same as the previous but in mm/dd/yy format.

  • %w: This shows the date in day-dd format.

  • %T: This displays the current time of the day, 24-hour format.

  • %t or %@: Same as the previous, uses a 12-hour, am/pm format.

  • %*: Same as the previous, also displays seconds.

Text formatting options

Unlike the previous escape sequences, these need to be opened and closed around the desired part of the prompt. That is, in order to underline word, you need to type it as %Uword%u. Pay special attention to the difference in the case of the opening (UPPERCASE) and closing (lowercase) escape sequences, as shown in the following points:

  • %U %u: This enables underline mode.

  • %B %b: This enables boldface mode.

  • %K %k: This sets the background color. Use it as %K{red}%k.

  • %F %f: Like the the previous, but applies to the foreground color.

  • %S %s: This enables standout (highlight) mode.

When dealing with escape sequences, both % and ) are somewhat special as far as zsh is concerned; thus, remember to type %% if you need to display a literal % on your prompt. Likewise, a literal ) should be typed as %). This technique is commonly referred to as escaping characters.

Note

You can enable the PROMPT_BANG option on your zsh configuration to use a bang (!) in your prompt in order to display the current history event number instead of having to escape it (%!). Just remember to type !!, should you require a literal !.

setopt PROMPT_BANG # enables '!' substitution on prompt

Conditional expressions

We will conclude our trip of the escape sequences by taking a look at the escape sequences available for conditional expansion. Luckily though, most of it can be summed up as the following ternary expression:

%(X.true-text.false-text)

Basically, what this means is that if the condition X is true, do whatever is in true-text, otherwise do whatever is in false-text. The important thing to remember is that you should wrap your expression with %(), and that the dots (.) you see there are completely arbitrary, meaning you can replace both of them with whatever character you like.

Regarding the true-text/false-text expressions, the manpage (as when you visit man zshmisc) tells us that they can be replaced with the likes of !. This will evaluate to true if the shell is running with privileges or ?, which in turn can be preceded by an integer n and will evaluate to true only if the exit status of the last command matches. Thus, in order to display # as your main prompt to signal whether you are running on elevated privileges, with a bit of imagination, you can come up with things like the following:

PS1=%(!.#.>)

Likewise, you could use the following line to wrap the exit status of the last command that was run, if it was other than 0, that is:

PS1=%(?..(%?%))

Putting it all together

As you are more than aware by now, zsh has many great features built in its prompt themes. So many in fact, that most of the time our custom solutions might feel like reinventing the wheel. We still need to take a shot at building our own prompt though; so, how about using one of the included themes as a starting point?

Navigate to your zsh installation folder or repository clone, and navigate to the Prompts folder under Functions. As we saw earlier, all prompts come with a setup function that follows the prompt_<theme_name>_setup naming pattern. Look for the setup file for the SuSE theme and open it. It will most likely be under prompt_suse_setup.

What you see there is a shell function that goes by the same name as the file. A single call to this prompt_suse_setup function, with no parameters passed, is all that it takes to make two assignments—one for the PS1 prompt and the other for PS2. Have a look at the following code, which has been formatted for this example:

PS1="%n@%m:%~/ > "
PS2="> "

So let's get started with hacking that prompt to pieces, shall we? Open your .zshrc file, and remember you will be adding the following line after the call to promptinit. We can start by highlighting the username, just like in the adam1 prompt:

PS1="%K{yellow}%n%k@%m:%~/ > "

If you recall from the previous section, the %K%k escape sequence defines the background color. Highlighted in the code, we wrap the escape sequence, %n, to add some background color to the current session, $USERNAME. On the right-hand side of the @ symbol remains the short version of the machine name and some fancy line indicators, of course.

Let's add an error flag to the right-hand side, so we can check immediately for an abnormal command exit code:

RPS1="%(?..(%?%))"

If you feel like it, you can test our brand-new right-hand prompt by calling a program in a way that will end abnormally. Remember, an exit status of 0 is ok; everything else will trigger our prompt. Something such as ls some_nonexistent_folder should be enough:

gfestari@machine:~/ > ls nonexistent_folder
ls: cannot access nonexistent_folder: No such file or directory
gfestari@machine:~/ >                                  (2)

You can sparkle some color into our right-hand prompt like we did for PS1. When you are done with your tweaking, try to leave .zshrc resembling the following code as much as possible:

autoload -U promptinit
promptinit

PS1="%K{yellow}%n%k@%m:%~/ > "
PS2="> "
RPS1="%(?..(%?%))"

We left the autoload -U promtpinit and promptinit calls in the previous example, so the prompt module would be loaded and ready for use, should you eventually require its services. Note, however, that you do not require both these calls unless you are planning on using the prompt module.

Save your file and let's reload zsh configuration. We do this by sourcing the .zshrc file one more time. Be careful though as this could take a while depending on the links to other files you might have added:

% source ~/.zshrc

Tip

source has a leaner and meaner brother: the dot (.) alias. Now that you've met him, feel free to do things such as the following:

% . ~/.zshrc

How about we take advantage of the whole width of the terminal emulator's window? You know, because widescreen.

A particularly useful on-screen help is the current directory shortcut, which if you recall can be either %~ or %d. So, how about we add a bit more context information to that lazy right-hand side prompt?

RPS1=%~

Come on, I know you didn't just think it was going to be that easy, right? We are adding functionality here, so it's not just about ditching our exit status indicator. Think about it; we need to add the current working directory to that right-hand prompt. Your first guess might be along the lines of the following command:

# this won't work!
RPS1=%(?..(%?%)) %~

This is almost perfect, save for the fact that it won't work straightaway.

% source .zshrc
> job not found: ~

Bummer! However, the slight detail that's missing is the usage of double quotes. That's right, we can sneak those spaces through the shell's string processing and come out with no errors just by using double quotes, as follows:

RPS1="%(?..(%?%)) %~"

This will tell the prompt function to take the RPS1 variable as it is and to not worry about parsing multiple parameters.

And, that's it. You have your own version of the prompt on your brand-new installation of zsh. Although, you might be wondering what's the deal with the second prompt that we left there. I'll leave it for you to decide its fate, as I really like the current old-school > indicator.

Before we are done with this chapter however, I'd like to point you towards the PROMPT THEMES section in the zshcontrib(1) manpage. Go ahead and type man zshcontrib on the terminal emulator of your choice for more detailed information when creating your own prompt themes.