T••LBX: Blog

Zshell path modifiers

Disclaimer: Zshell is the shell used for this article. It does not mean that these modifiers are not available in other shells, or that there is no other way to do the same thing in other shells. Look it up if you're not sure.

You probably already know a few ways to transform variables inherited from Bash. If not, you should check them out because they are really useful. They allow you to get a substring, substitute a word for another, etc. Here are a few variable modifiers for handling paths.

One common use is to have a path variable and you want to extract the directory name or the extension. You could use commands for this like dirname or basename, but there is a better way than starting a process. There are built-in modifiers. You'll find many examples on the internet which remove something before or after a certain character, but there are better modifiers for this.

Let's put a path in a variable:

$ MYPATH="/path/to/image.png"
$ echo $MYPATH

There are 2 ways of splitting a path. The first is head/tail, which splits at the last slash.

$ echo $MYPATH:h
$ echo $MYPATH:t

The second way is root/extension, which splits at the last dot.

$ echo $MYPATH:r
$ echo $MYPATH:e

If you want to get only the name of the file without the extension, you can combine tail and root.

$ echo $MYPATH:t:r

There is a lot more to this but if you use them in a string, it is safer to use curly brackets around the variable.

$ echo "This is my file name: ${MYPATH:t:r}"
This is my file name: image

Now another usefull one which gives you the absolute path of the variable.

$ IMGPATH=img.png
$ echo ${IMGPATH:a}

# It works with nesting
$ IMGPATH=images/img.png
$ echo ${IMGPATH:a}

# It works with .. and . as well
$ IMGPATH="../victor/img.png"
$ echo ${IMGPATH:a}

Note that the paths we make absolute do not have to exist. The modifier just outputs what it would be based on the current directory. And you can use :A instead if you want to resolve symbolic links.

Now the :c modifier outputs the absolute path of command name, using the PATH variable, like what the which command would do.

$ echo ${MYCOMMAND:c}

It works with regular arguments as well

If you put it in backets, you can use these on regular arguments, even when globbing. This command for example prints all the C filenames (without extension) nested in the src directory.

$ print -l src/**/*.c(:t:r)

Other modifiers

I know the title of this article refers to "path" modifiers but they don't have to be used on paths as long as they do what you want. And equally, some modifiers were not created especially for paths, but can be used on them anyway. Here are a few.

Another useful modifier that could be used on path or anything else is the :s modifier. It is the typical substitute that you find in grep and in many other softwares. Here we copy all text files into another one with the markdown extension.

for file in *.txt; do
  cp ${file} ${file:s/txt/md/}

Like many substitution tool, it works only one time. If you want to substitute multiple times, use :gs instead.

$ MYFILE=css/main.css
$ echo ${MYFILE:gs/css/js/}

Also like many substitution tools, you can use any character for the delimiter. This is handy when you want to replace a slash because otherwise you would have to escape it.

$ MYFILE=img/large/01.png
$ echo ${MYFILE:gs_/_._}

You can quote text with :q and unquote it with :Q.

$ MYVAR="Hello zshell 'world'"
$ echo ${MYVAR:q}
Hello\ zshell\ \'world\'

$ MYVAR="Hello\ zshell\ \'world\'"
$ echo ${MYVAR:q}
Hello zshell 'world'

You can obviously make a variable all lowercase or all uppercase.

$ echo ${MYVAR:l}
make me whisper

$ MYVAR="make me shout"
$ echo ${MYVAR:u}

There is also a way to capitalize words. I won't go into details explaining why it looks different, but here is how you do this:

$ MYVAR="hello world"
$ echo ${(C)MYVAR}
Hello World

Finally a very useful functionality is substring extraction. For this you use 2 numbers representing the index of the first character and the last one (excluded). For some reason, a negative index (from the end) works on the second number, but not the first one. Don't hesitate to contact us is you know why.

$ MYDIGITS="0123456789"
$ echo ${MYDIGITS:0:4}

$ echo ${MYDIGITS:0:-2}

# You can omit the second number if you want the end of the string
$ echo ${MYDIGITS:2}


This list of modifiers is not complete and there are a lot of other things to learn about zshell. I encourage you to read more about it. This zsh lovers page is a good start.

Tags:     shell zshell zsh