Zsh
Shell Scripting Primer
There are a handful of shell scripting languages, many of which will come by default with your operating system. On both macOS and Linux based operating systems, you can count on having at least one of the following shells by default.
sh
: the Bourne shell, written in 1977 for Unixksh
: the Korn shell, written in 1983 by David Korn for Bell Labsbash
: the Bourne Again shell, written in 1989 by Brian Fox for GNUzsh
: the Z shell, written in 1990 by Paul Falstad, released open source under the MIT license
This guide uses zsh
as the shell language of choice, so your mileage may vary if you try to use these commands in another shell scripting language. The reason zsh
is chosen is because it's the default shell on the macOS operating system, but more importantly, because it is my favorite shell
Key Concepts
This guide is more of a reference than a tutorial, so expect the sections to jump around a fair bit.
People often forget the distinction between argument, option, and parameter
I/O
read
getopt
There exists a builtin command getopts
but it does not accept long-form command arguments. Therefore, it is useful to use GNU's getopt
utility function to parse command-line arguments in shell scripts.
Useful Flags:
-o
or--options
, specify the short-form options that can be supplied to this program-l
or--long
, specify the long-form options that can be supplied to this program
For each option declared after the --options
or --long
flag, that option can be proceeded by 1 colon, indicating that this option has a required argument, or by 2 colons, indicating that this option has an optional argument
It's a little easier to explain with an example:
if [[ $# -eq 0 ]]; then
print "Error: no options provided" >&2
exit 1
fi
# Call getopt to validate the provided input.
options=$(getopt -o ho:i:w:: -l help,output:,input:,where:: -- "$@")
if [[ $? -ne 0 ]]; then
print "Error: incorrect options provided" >&2
exit 1
fi
eval set -- "${options}"
while true; do
case "$1" in
-h|--help)
print "I'm sorry Dave, I'm afraid I can't do that"
;;
-o|--output)
ofile=$2
shift 2
;;
-i|--input)
ifile=$2
shift 2
;;
-w|--where)
case "$2" in
"")
location="not specified"
shift 2
;;
*)
location="$2"
shift 2
;;
esac
;;
--)
shift
break
;;
esac
done
if [[ ${ifile} ]]; then
print "Input file is ${ifile}"
fi
if [[ ${ofile} ]]; then
print "Output file is ${ofile}"
fi
if [[ ${location} ]]; then
print "Location is ${location}"
fi
Tilde Expansion & Wildcards
*
matches any string of characters?
matches any single character.file.?
, for instance, would matchfile.c
andfile.o
but notfile.cpp
[abc]
will match a single character, either a, b, or c.
Login Shells
A login shell is started when you open your terminal, or login to another computer via ssh
. But here is where it gets tricky. If you open your terminal, and you see your shell prompt, opening up a new shell inside of it would not be a login shell.
zsh
And yet, the following would be a login shell, because it uses the -l
flag to log in.
zsh -l
Run each of these commands below to help you test whether or not your shell is a login shell:
# Run this command first
if [[ -o login ]]; then; print yes; else; print no; fi
# Enter a non-login shell
zsh
# Run this command second
if [[ -o login ]]; then; print yes; else; print no; fi
# Exit the non-login shell that you just opened up
exit
A script is non-interactive, since it's executed as a command, and freezes your terminal until you finish. However, a script will be treated as interactive if it is executed with the -i
flag. You can test this with the code below.
A shell is interactive as long as it was not started with either a non-option argument or the -c
flag.
case "$-" in
*i*) print This shell is interactive ;;
*) print This shell is not interactive ;;
esac
Subshells will retain the value of variables exported to the environment.
In order to create a subshell with a clean environment, you need to pass
specific commands to exec
and zsh
, as shown below:
-
Create a subshell with a clean user environment
exec -c -l zsh -d -f
A breakdown of the arguments provided below
exec
-
-c
: clear the environment -
-l
: simulate login shell (prepend-
toargv[0]
)
zsh
-
-d
: ignore global runtime configuration files -
-f
: ignore user runtime configuration files
-
User and System runtime configurations
It comes down to whether the files are in /etc
or ${HOME}
.
-
An rcfile located in
/etc
will load for any user on the machine. -
An rcfile located in
~/
will load for only that user. -
When a shell is a login shell, it will source
/etc/zprofile
and~/.zprofile
in that order. -
When a shell is a interactive shell, it will source
/etc/zshrc
and~/.zshrc
in that order. -
Regardless of the shell,
zsh
will source/etc/zshenv
and~/.zshenv
in that order. -
Trace execution of files sourced on startup:
zsh -o SOURCE_TRACE
Command Substitution
Sometimes you're in a situation where you'd like to run a command, but you don't know what the input value should be yet. Yes, you could save a variable, but that wouldn't be the properly lazy way of doing things. You can treat a substitute the output of a function by placing it inside $(here)
Using command substitution allows us to take the output from a command, and use at as the input for a different command. In the following example, the output of the command whoami
is substituted as input for the command print
:
print "My name is $(whoami)"
My name is ttrojan
Parameter Expansion
-
Example of parameter expansion
name='Austin' print My name is ${name}
My name is Austin
Expansion Modifiers
-
${parameter:-word}
- If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
-
${parameter:=word}
- If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional parameters and special parameters may not be assigned to in this way.
-
${parameter:?word}
- If parameter is null or unset, the expansion of word (or a message to that effect if word is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.
-
${parameter:+word}
- If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.
-
${(P)parameter}
-
Normally,
${parameter}
will expand to become the value of the variable whose identifier isparameter
. By prepending the(P)
parameter expansion flag, the shell will instead perform two dereferences. The value returned will be the value of the identifier whose name is stored in${parameter}
.for parameter in XDG_{DATA,CONFIG,CACHE}_HOME; { print "${parameter} -> ${(P)parameter}" }
XDG_DATA_HOME -> ~/.local/share XDG_CONFIG_HOME -> ~/.config XDG_CACHE_HOME -> ~/.cache
-
Conditional Expressions
When coding, we typically expect less than to be represented by the <
character. In shell scripting, however, the <
symbol has an entirely seperate
meaning (more on that later). To perform an equality check, we have to use -lt
to signify the same meaning. Also, we will use square brackets [[ ]]
to
contain the statement, and literally specify then
as well as the end of our if
statement. An example is provided below.
name='Austin'
if [[ ${name} == 'Austin' ]]; then
print "His name is Austin"
else
print "his name is not Austin"
fi
-
Check the user
# Check if the script is being executed by the root user if [[ ${UID} -ne 0 ]]; then print "You are not the root user"; fi
All Conditional Flags
Comparator | Meaning |
---|---|
-eq |
is equal to |
-ne |
is not equal to |
-lt |
is less than |
-le |
is less than or equal to |
-gt |
is greater than |
-ge |
is greater than or equal to |
-z |
is null |
Arithmetic Evaluation
number=4
if (( number < 5 )); then
print "Number is less than five"
else
print "Number is not less than five"
fi
Output
Number is less than five
In order to perform arithmetic operations, surround variable names, integers, and operators in a ((...))
double quotations, like this:
-
Adding to
1
the number2
value=1 ((value+=2)) print ${value}
3
If you don't do that, the variable is interpreted as a string, and the number will be appended to the variable's current value.
-
Appending to
1
the character2
# Appending '1' to the string '2' value=1 value+=2 print ${value}
Output
12
ls
Useful Flags
Flag | Purpose |
---|---|
-A |
Reveal hidden files (except . and .. ) |
-l |
List in long format |
-h |
Describe file-size in terms of G , M , K , etc. |
-k |
Describe file-size in terms of kilobytes |
-t |
Sort by time modified (newest -> oldest) |
-u |
Sort by time last accessed (newest -> oldest) |
-S |
Sort by size (largest -> smallest) |
-r |
List the files in reverse lexicographical order |
Colorized ls
-
If you're on macOS, you're likely using BSD's
ls
. To colorize the output ofls
, include the-G
flag or addtypeset -xg CLICOLOR=1
to your runtime configurations. -
If you're using GNU's
ls
, supply the--color=auto
argument to colorize the output ofls
.
Escaping Characters
Some characters take on a special meaning when they are escaped
Character | Escaped Meaning |
---|---|
\a |
alert (bell) |
\b |
backspace |
\e |
escape |
\n |
newline |
\r |
carriage return |
\t |
tab |
\x41 |
1-byte UTF-8 character |
\u2318 |
2-byte UTF-8 character |
\U0001f602 |
4-byte UTF-8 character |
Some characters are special by default, and must be escaped by the escape character \
in order for zsh
to interpret them literally
Character | Un-Escaped Meaning |
---|---|
\ |
Escape character |
/ |
Pathname directory separator |
$ |
Variable expression |
& |
Background job |
? |
Character wildcard |
* |
String wildcard |
( |
Start of sub-shell |
) |
End of sub-shell |
[ |
Start character-set wildcard |
] |
End character-set wildcard |
{ |
Start command block |
} |
End command block |
` | ` |
! |
Logical NOT |
; |
Command separator |
' |
Strong quote |
" |
Weak quote |
~ |
Home directory |
` | Backtick |
# |
Comment |
Some characters are special, but only some of the time, such as ,
, for example, in the case of brace expansion
print cod{e,er,ing}
code coder coding
Single Quotes
If a string is inside of single quotes, every character inside will be preserved literally, thus no escaping is possible. The only character that can't be typed is the single quote '
which signals the end of the string.
Double Quotes
Double quotes are a little more permissive than single quotes.
- The dollar sign
$
retains its special meaning. - The backtick ` retains its special meaning.
- A back-slash
\
only retains its special meaning when escaping a$
, `,"
or a newline character.
Run a job in the background
When a program is run in the background, the program is forked, and run in a sub-shell as a job, running asynchronously. You can add a single ampersand &
at the end of a command to have it run in the background.
-
Run a program in the background
python example.py &
-
Run multiple programs in the background (with only a single command)
python one.py & python3 two.py & python3 three.py &
Run commands sequentially
You can use a double ampersand &&
to signal for commands to run in sequential order. The second command will only run if the first command doesn't fail. A program fails if it returns a number other than 0.
-
Only run the Python program
second.py
if the Node programfirst.js
ended successfullynode 'first.js' && python 'second.py'
You can use a double pipe ||
to signal for a command to run only if the previous command fails.
-
If Bash program
attempt.rb
did not end successfully, run the Ruby programbackup.sh
bash 'attempt.rb' || ruby 'backup.sh'
/dev/null
Your own personal black hole
/dev/null
is a very useful tool. If you're ever in a situation where you need to redirect output, but you have nowhere to put it, you can redirect it to /dev/null
. This will accept input and essentially vaporize it. You won't see anything printed to your terminal, and nothing will be saved to a file.
Run this command in your terminal to see what happens.
print "Silenced" &> /dev/null
However, there's an even easier way to do it. You can combine stdout
and stderr
, file descriptors 1
and 2
respectively using the &>
redirection command, and then append a -
to close both of the file descriptors.
Easier to demonstrate with an example
func() {
print "Standard Output" >&1
print "Standard Error" >&2
}
-
Execute
func
with both file descriptors openfunc
Standard Error Standard Output
-
Execute
func
with both standard output closedfunc 1>&-
func:1: 1: bad file descriptor Standard Error
-
Execute
func
with both standard error closedfunc 2>&-
Standard Output func:2: 2: bad file descriptor
Shell Arguments, Options, Flags
Sometimes you want to specify an option/flag (same thing)
Maybe -v
should print verbose output. But other times, there's an argument associated with the flag, such as -o file.txt
. In this case, file.txt
is known as an option argument
Parsing Command-Line Arguments
The keyword ${@}
contains the set of all arguments to a program/function
-
Printing out all of the arguments to a function
func() { for arg in ${@}; do print "Argument: ${arg}" done }
func 'one' 'two' 'three'
Argument: one Argument: two Argument: three
Reading I/O
read
-
Prompt for input with
Write some text:
, save to variablevariable
read -r 'variable?Write some text: '
-
Prompt for password, save to variable
# Save the result in the variable 'secret' read -rs 'secret?Password:' print "You entered ${secret}"
-
Pass each word from piped input into array
words
print "alpha bravo charlie" | read -A words print -l ${words} # alpha # bravo # charlie
-
Read each line of
file.txt
into an arraytext=$(<file.txt) lines=(${text// /\\ })
-
Save contents of
/dev/stdin
to variabletext
-
Fast form (command substitution + file redirection)
text=$(<&0)
-
Slow form (
read
builtin)read -u 0 -d '' text
-
-
Printing all of the files in a directory
print -l ./dir/*(.)
-
Print the names of subdirectories found within packages installed to
/usr/local/opt
:print -l /usr/local/opt/*/*(/:t) | sort | uniq
Looping
While loops
typeset -i index=0
while (( ${index} < 5 )); do
print ${index}
# Lame variable increment
index=$((index+1))
# L33t variable increment
((index+=1))
done
Anonymous Functions
Zsh supports anonymous functions, which allow us to prevent variables from leaking in scope.
This is particularly useful when you're building a script that will be sourced
by the shell, such as .zshenv
or .zshrc
. Ideally, we'd prevent our script
from polluting the shell environment with variables that are left lingering at
the end of our script's execution. We can do so by nesting our code inside of
anonymous functions.
Without using an anonymous function, the identifier used as the iterator in a for-loop persists beyond the evaluation of the for-loop itself:
integer i
for i in {1..3}; do
print ${i};
done
integer -p i
Output
typeset -i a=3
By nesting our declaration of the for-loop iterator within an anonymous function, we can prevent the scope of the variable from leaking into the greater namespace
(){
integer i
for i in {1..3};
do print ${i};
done
}
integer -p i
Output
integer: no such variable: i
You can also use the pre-increment (++i
) and post-increment (i++
) operators
within the double parenthesis block (( ))
-
Using the pre-increment operator:
typeset -i a b a=10 (( b = a++ )) print "a=${a}\nb=${b}"
Output
a=11 b=10
-
Using the post-increment operator:
typeset -i a b a=10 (( b = ++a )) print "a=${a}\nb=${b}"
Output
a=11 b=11
String Manipulation
String manipulation allows you to rename files, implement boolean logic, along
with many other uses. Every variable stored as string can be referenced with
the syntax ${string:position:length}
Index Slicing
-
Print the first three numbers
val='0123456789' print ${val:0:3}
012
-
Print every number after index 5
val='0123456789' print ${val:5} # 56789
56789
-
Print the last 3 numbers
val='0123456789' print ${val:(-3)}
789
-
Print everything except the first 2 numbers and last 3 numbers
val='0123456789' print ${val:2:(-3)}
23456
-
Print two numbers starting from the 6th-to-last number
val='0123456789' print ${val:(-6):2}
45
-
Print the last 9 letters of a scalar type variable
# Not a real SID, to be clear. TWILIO_ACCOUNT_SID=1f024f2f13r123456789 print ${TWILIO_ACCOUNT_SID[-9,$]}
123456789
Substring Matching
If you're looking for a way to remember these, there's a trick I use:
Look down at your keyboard
#
is on the left, so it cuts off the left-side.$
looks like an S, so it's the string.%
is on the right, so it cuts off the right-side.
string#pattern
: Delete the shortest possible match ofpattern
from the front ofstring
.string##pattern
: Delete the longest possible match ofpattern
from the front ofstring
.string%pattern
: Delete the longest possible match ofpattern
from the end ofstring
.string%%pattern
: Delete the longest possible match ofpattern
from the end ofstring
.
string='one/two/three/four/five'
print ${string#*/} # two/three/four/five
print ${string##*/} # five
print ${string%/*} # one/two/three/four
print ${string%%/*} # one
Length of a String
checksum=${(s< >)$(shasum -a 256 file.txt)[1]}
print ${(N)checksum##*}
# 64
Cutting Out The Middle
Using the parameter expansion flag (S)
, you can actually specify for the pattern to match substrings, similar to the way grep
and sed
work. For the #
and %
parameter expansion flags, they will still seek to cut from the beginning and the end respectively, but will cut out the first match found to the pattern (non-greedy) from the middle of the string. You can use ##
and %%
to perform greedy searches.
-
Remove the (S)ubstring
two
on the leftstring='one/two/three/two/one' print ${(S)string#two}
one//three/two/one
-
Remove the (S)ubstring
two
on the rightstring='one/two/three/two/one' print ${(S)string%two}
one/two/three//one
-
Extract the (M)atching (S)ubstring in the middle
string='one/two/three/two/one' print ${(MS)string#/t*o/}
/two/three/two/
-
Non-greedy match starting from the left
string='the quick brown fox' print ${(MS)string#q*o}
quick bro
-
Greedy match starting from the left
string='the quick brown fox' print ${(MS)string##q*o}
quick brown fox
Splitting Strings
You can index a string by its word index (1-indexed), even if there is punctuation in the sentence by using the (w) flag inside of square braces.
var='This sentence has inconsistent spaces'
print ${var[(w)5]}
spaces
var='Sentence one. Sentence two.'
print ${var[(w)4]}
two.
var='You can even get the word that comes last'
print ${var[(w)-1]}
last
-
Strip any leading/trailing whitespace from a parameter:
typeset var=$'\n\t abc def \t\n' # Strip leading/trailing whitespace from ${var} var=${(MS)var##[[:graph:]]*[[:graph:]]} # Print the value of ${var} after having removed leading/trailing whitespace print -n ${var}
Output:
abc def
Referencing Command History
-
!!
the previous command and all arguments -
!*
the previous command's arguments -
!^
the previous command's first argument -
!$
the previous command's last argument -
!:2
the previous command's second argument -
!^-
: all the arguments of the previous command except for the last argument -
!:-
: the previous command except the last argument -
!#
the current command typed thus far -
!grep
: the most recent command starting withgrep
-
!?string?
: the most recent command containingstring
-
!-2
the penultimate command -
!#:0
the command being typed -
!#:2
the second argument of the current command being typed
Next, attached below are expansions for arguments outside the context of command history
-
$_
an environment variable storing the last argument of the previous command -
$$
the process ID itself -
$*
a string containing all arguments entered on the last command -
$1
the first argument supplied to a command (if accessing command arguments from within a function script) -
$2
the second argument supplied to a command (if accessing command arguments from within a function or script) -
$@
all arguments supplied to a command (if accessing command arguments from within a function or script) -
$?
the return value of the previous command -
$-
the current options set for the shell (the single letter option names concatenated into a string) -
Copy the last command to your pasteboard
pbcopy < =(<<<"!!")
-
Reference the first argument of the previous command
print first second third print !^ # => "first"
-
Reference the last argument of the previous command
print first second third print !:$ # => "third"
-
Reference the second argument of the previous command
print 'first' 'second' 'third' print !:2
print 'second'
second -
Reference all arguments of previous command, excluding the last argument
print first second third print !:^- # => This command would call `print 'first' 'second'`
-
Reference the second-to-last command
print 'three' print 'two' print 'one' !-2 # This command would call `print 'two'`
Substituting Text in Previous Commands
# [ Option 2 ]
^brown^blue
print the quick blue fox
Global Substitution:
Using the previous syntax, you will only replace the first instance matched. If you want to replace all matches to the pattern, use the syntax below:
-
Replace the first match to a pattern:
attitude="it is what it is" print ${attitude:s/is/be}
it be what it is
-
Replace all matches to a pattern
attitude="it is what it is" print ${attitude:gs/is/be}
it be what it be
Directory Expansion
~+
: Expands to$PWD
~-
: Expands to$OLDPWD
Loading Bar
You can use ANSI escape codes to make a loading bar
for i in {1..100}; do
# Print the loading as a percentage, with color formatting
print "Loading: \x1b[38;5;${i}m${i}%%\x1b[0m\n"
sleep 0.01
# Return to the previous line above
print "\x1b[1F"
done
# Return to the line below
print "\x1b[E"
for i in {1..255}; do
print "\x1b[38;5;${i}mwow\x1b[0m\n"
sleep 0.01
print "\x1b[1F"
done
# Return to the line below
print "\x1b[E"
Read Words Into Array
-
Read in each word of
file.txt
into the array${words}
(file.txt) the day is sunny and the sky is blue
words=($(<file.txt)) print "There are ${#words} words" print ${words}
Output
There are 10 words the day is sunny and the sky is blue
Sending Signals With Kill
The builtin command kill
is used to send signals to a process.
You can specify the signal by its number, or by its name.
Handling Signals With Trap
TRAPINT() {
print "TRAPINT() called: ^C was pressed"
}
TRAPQUIT() {
print "TRAPQUIT() called: ^\\ was pressed"
}
TRAPTERM() {
print "TRAPTERM() called: \`kill\` command received"
}
TRAPEXIT() {
print "TRAPEXIT() called: happens at the end of the script no matter what"
}
for i in {1..5}; do
print ${i}
sleep 1
done
For all of these TRAP[NAL]()
functions, if the final command is return 0
(or if there is no return statement at all, then code execution will continue where the program left off, as if the signal was accepted, caught, and handled. If the return status of this function is non-zero, then the return status of the trap is retained, and the command execution that was previously taking place will be interrupted. You can do return $((128+$1))
to return the same status as if the signal had not been trapped
fc
"find command"
-
List the last 10 commands in history
fc -l -10
-
List commands number 800 through 850 in history
fc -l 800 850
-
List all commands in history that started with
sudo
fc -lim 'sudo *' 1
Globbing
-
Remove files that haven't been accessed in more than 1 day
# For files (not directories) in `~/tmp`, list those # that haven't been accessed in at least 1 day for file in ~/tmp/**/*(.ad+1); do rm ${file} done # `(.)` select files, but not directories # (ad+1) access time, >1 (d)ays from the present moment
-
Select all files in the current directory ending in a number, no matter how many digits! 🤯
./file0 ./file12 ./file001print -l ./*-<->
Note that this is not sorted numerically. However, it is possible to specify this.
To do so, specify the glob qualifier n
in your filename generation pattern, such as in the example below.
-
Sort files numerically
./file0 ./file001 ./file12print -l ./*-<->(n)
Operator Expansion
If name
is an associative array, the expression ${(k)name}
will expand to the list of keys contained by the array name
-
(k)
Print the keys of a assorted array:declare -A dict dict[a]=alpha dict[b]=bravo dict[c]=charlie print ${(k)dict}
a b c
-
Filter a key from an associative array:
typeset -A map=([a]=alpha [b]=bravo [c]=charlie) typeset -a filter=(b) print -- ${#${(k)foo}} print -- ${#${(k)map:|filter}}
The output is the following:
3 2
-
(s)
Split a string into an array, separating array entries on the occurance of a delimiter, which is removed from the elements. The delimiter can be specified by placing it within any of the following matching pairs:(...)
,{...}
,[...]
, or<...>
string="one:two:three" print -l ${(s<:>)string}
one two three
-
(j)
Join an array into a string, uniting each entry with a delimiter, which is inserted between the elements in the string. The delimiter can be specified by placing it within any of the following matching pairs:(...)
,{...}
,[...]
, or<...>
list=(one two three) print ${(j<, >)list}
one, two, three
-
(w)
: With${#name}
, count words in arrays or strings, optionally using the(s<...>)
flag to specify word delimiterlist=(one two three four) print ${(s<,>w)#string}
4
-
(W)
: Same as(w)
, but where empty words between repeated delimiters are also counted
Globbing
zsh
is capapable of some very powerful globbing. Without setting any options, you can recursively iterate through directories with **/*
.
Glob Options
-
setopt NULL_GLOB
: If a glob pattern is not matched, don't return an error, return instead an empty string. -
setopt EXTENDED_GLOB
will enable special glob patterns, examples provided below:# Select any file in any directory, whose parent directory is not 'src', 'bin', or 'lib' ./*/(*~src~bin~lib)/*(.); do
Included below are some features of the extended glob option:
^x
Matches anything except the patternx
. This means that^foo/bar
will search directories in the present working directory except for./foo
for a file namedbar
Glob Qualifiers
This topic is covered in greater detail on
the zsh.fyi
article about expansion
Here are some flags below:
-
(:a)
: return each globbed file's absolute path.print ./*(:a) # ./example.txt => /Users/tommy/example.txt
-
(:P)
: return each globbed file's absolute path, resolved of any symbolic linksprint ./*(:P) # ./example.txt => /Users/tommy/example.txt => /Users/tommy/real/example.txt
-
(:A)
: return each file's absolute paths, trying to resolve symbolic links, falling back on the absolute file to the symlink file itself if the directory it points to does not exist.# [ Using (:A) ] ln -s /fake/path ~/Desktop/example print ~/Desktop/example(:A) # => /Users/austin/Desktop/example # [ Using (:P) ] print ~/Desktop/example(:P) # => /fake/path
-
(:e)
: strip everything but the extension from each globbed fileprint ./*(:e) # ./example.txt => txt
-
(:r)
: strip the extension suffixprint ./*(:r) # ./example.txt => ./example
-
(:t)
: strip all of the leading directories from the filepathval="./path/to/file.txt" print "${val} => ${val:t}" # ./path/to/example.txt => example.txt
-
(:h)
: strip one trailing pathname component from the filepathval="./path/to/file.txt" print "${val} => ${val:h}"
./path/to/file.txt => ./path/to
-
Print the absolute path to the file currently being sourced/executed
-
Option one
echo ${ZSH_SCRIPT}
-
Option two
echo ${${(%):-%N}:A}
Output
/usr/local/bin/filename.sh
-
-
Print the absolute path to the directory containing the file currently being sourced/executed
filepath=${${(%):-%N}:A:h} print ${filepath}
Globbing Specific Filetypes
Below are some qualifiers related to the type of file
Glob Qualifier | Meaning |
---|---|
/ |
directories |
. |
plain Files |
= |
sockets |
* |
executable files |
% |
device files |
@ |
symbolic Links |
Below are some qualifiers related to the access permissions of the file
Owner | Group | World | |
---|---|---|---|
Readable | r |
A |
R |
Writable | w |
I |
W |
Executable | x |
E |
X |
Additionally, I've included some extra examples below
Glob Qualifier | Meaning |
---|---|
F |
Full directories |
^F |
Empty directories and all non-directories |
/^F |
Only empty directories |
s |
setuid files 04000 |
S |
setgid files 02000 |
t |
sticky bit files 01000 |
# All plain files
print ./*(.)
# Anything but directories
print ./*(^/)
# Only empty directories
print ./*(/^F)
# [ Recursive Editions ]
# All plain files
print ./**/*(.)
# Anything but directories
print ./**/*(^/)
You can use o
in conjunction with some other keywords to sort the results in
ascending order
-
on
Name -
oL
Filesize -
oa
Time Accessed -
om
Time Modified -
oc
Time Created -
odon
Sort by names for files within the same directory -
*(^-oL)'
Sort all files by file size in descending order, resolving any symbolic links -
Print all of the directories in descending order of size, in an escaped format to be re-usable by the shell
print ./*(/OL:q)
-
Select the largest regular file within a directory
# 'L': (normally) sort by length (of file, i.e. its size), ascending # (Using ascending order, and picking the last element) print ./*(.DoL[-1]) # (Using descending order, and picking the last element) # 'O': reverse order print ./*(.DOL[1])
-
Select all files larger than 2MB in a directory
# 'm' (megabytes) (and 'k' for kilobytes) # '-' (smaller than 2) print ./*(.Lm-2)
-
Select the most recently modified file within a directory
print ./*(.om[1])
-
Select all files modified within the last hour
# 'M' for Months # 'w' for weeks # 'h' for hours # 'm' for minutes # 's' for seconds # '-': modified less than '#' hours ago # '+': modified more than '#' hours ago print -l ./*(.mh-1)
-
Open all files created within the last 2 days
open ./path/to/dir/*(.c-2)
-
Add each directory to the
${folders}
array, but only if it exists# Using # (N) enable null glob # (/) only match an existing directory typeset -a folders folders=( /usr(/N) /bin(/N) /asdf(/N) ) print -l ${folders}
/usr /bin
-
Select all files that aren't named
tmp
# '#': the delimiter between the expansion flag and the string # `$REPLY`: every file name specified by the glob ./* print -l ./*(e#'[[ ! -e $REPLY/tmp ]]'#)
Checking if a Command Exists
Using equals expansion
A simple way to perform a check is by using equals expansion (e.g. ), which will search the directories in path for an executable file named FILENAME
if [[ =brew ]]; then
print "Command is an executable file"
else
print "Command not found"
fi
Using parameter expansion
# [ Right way, note the (( parentheses )) ]
if (( ${+commands[brew]} )); then
print "Command exists"
else
print "Command not found"
fi
Count the Number of Words in a String
-
Count the number of characters in a string
sentence="Hello world" print ${#string} # => 13
-
Count the number of words in a string
sentence="Hello world" print ${(w)#string} # => 2
Reading Words
Below is an example of how to print a list of all words present in a file
words.txt
removed of any duplicates
<words.txt>
the day is sunny the the
the sunny is is
-
Reading in the file
words.txt
into arraywords
# Read in the file into an array of words words=($(<words.txt))
-
Printing all the words
print ${words[@]}
the day is sunny the the the sunny is is
-
Printing the unique words
print ${(u)words[@]}
the day is sunny
-
Printing the count of each word occuring in
words.txt
in descending orderwords=($(<words.txt)) # Create an array to store the word and its count declare -a count # For each (u)nique word for word in ${(u)words}; do # Add "<#> <word>" to the array count+=("$(grep -c ${word} <<< "${(F)words}") ${word}") done # Print the results, sorted (n)umerically # (O)pposite of ascending order for result in ${(On)count}; do print ${result} done
-
Solution using command line tools
# Short form tr -s ' ' '\n' < words.txt \ | sort \ | uniq -c \ | sort -r # Long form tr --squeeze ' ' '\n' < words.txt \ | sort \ | uniq --count \ | sort --reverse
-
Split each word in the string by the delimiter
:
string="alpha::charlie" # excluding the empty index value in the middle array1=(${(s_:_)string}) # using '_' as argument separator array1=(${(s[:])string}) # using '[]' as argument separator print ${#array1} # => '2' # including the empty index value in the middle array2=("${(@s_:_)string}") # using '_' as argument separator array2=("${(@s[:])string}") # using '[]' as argument separator print ${#array2} # => '3'
-
Create an array out of the lines outputted by a command
print -l ${(f)"$(networksetup -getinfo Wi-Fi)"}
-
Extract the second line of output from a command
print ${${(f)"$(networksetup -getinfo Wi-Fi)"}[2]}
-
Append
.old
to each scalar in the arrayfiles=( ./one.txt ./two.txt ./three.txt ) print -l ${files/%/.old} # => ./one.txt.old # => ./two.txt.old # => ./three.txt.old
-
Prepend
old.
to each scalar in the arraypeople=( man woman maid ) print -l ${files/#/old.} # => old.man # => old.woman # => old.maid
-
Print each unique word in a paragraph
string="this sentence is a sentence this line is part of the paragraph and this line is the end" words=(${=string}) print -l ${(u)words} # => this sentence is a line part of the paragraph and end
-
Print each word in lexicographic order
string="third fourth Second First" words=(${=string}) print ${(o)words} # => First Second Third Fourth
-
Given a string that includes tabs, spaces, and newlines, return an array of just the words
string=$'first\tsecond\nthird fourth fifth sixth' array=(${=string}) print ${#array} # 6
-
Passing escape sequences to a string
print $'name:\tAustin Traver\nid:\t1234' # => name: Austin Traver # => id: 1234
-
Check if a variable is set
# If variable "var" is set if [[ -v var ]] { print "Variable is set" } else { print "Variable is not set" }
Warning: Don't expand the value of
var
(e.g.${var}
) or the statement won't work -
Check if a variable is either unset, or is set, but is the empty string
if [[ -z ${var} ]] { print "Variable 'var' is either an unset variable or is a variable whose value is set to the empty string" }
-
C-style
for
loopfor ((i=0; i<10; ++i)); do print ${i} done
whence
The whence
command is very useful, and can replace many common commands
whence -v
is equivalent totype
whence -p
is equivalent topath
whence -c
is equivalent towhich
whence -c
is equivalent towhere
whence
is equivalent tocommand -v
Finding Commands Matching a Pattern
You can use the -m
option to match a pattern instead of a command name. Be sure to use a quoted string for your pattern, otherwise it is subject to filename expansion.
-
Finding commands starting with
print
whence -m 'print*'
print printf /usr/local/bin/printafm /usr/local/opt/coreutils/libexec/gnubin/printenv /usr/local/opt/coreutils/libexec/gnubin/printf
here-doc
Sometimes you want to supply some text in a script across multiple lines. Furthermore, this is happening at a point where you're already in some nested layers of indented logic. Luckily zsh
provides a way to supply a multi-line string, stripped of any leading \t
tab characters. It's called a here-doc
and it's referred to with the <<-
operator.
-
Storing the contents of a here-doc in
file.txt
:if [[ true ]]; then <<-EOF > file.txt 1 leading tab 2 leading tabs 3 leading tabs EOF fi
-
Using a here-doc to avoid printing leading tabs to stdout:
if [[ true ]]; then cat < =( <<-EOF this output is split along multiple lines as such, but they strip any leading tabs but not leading spaces EOF ) >&1 fi
-
Assigning a heredoc to a variable using Zsh:
read -r -d '' VARIABLE <<-EOF the first line the second line the third line EOF print -- ${ VARIABLE }
the first line the second line the third line
-
Printing the contents of
file.txt
:# Print the contents of 'file.txt' < 'file.txt' >&1 # [ Output ] # ========== # => 1 leading tab # => 2 leading tabs # => 3 leading tabs
-
Advanced example:
# Output to "basicquery.txt" the following # stripping away any leading tabs <<- EOF > basicquery.txt Today's date is $(date) The user ${USER} is on host ${HOST} This program launched on ${TTY} EOF # Read each line of "domains.txt" into an array # where each element of the array is a "domain" # Print the output of what occurs within the # curly braces to "output.txt" for domain in ${<(./domains.txt)}; { print "Performing DNS query on ${domain}" dig ${domain} } > output.txt
Here-String
A here-string
is documented exactly twice by name in the entire zsh
manual. Writing down how it works here, so that I know for next time...
-
Supply the string
hello world\n
as standard input to the current commandgrep 'world' <<< 'hello world'
-
Create the file
hello.txt
with the following contents-
Contents
hello world
-
Command
<<< $'hello\nworld' > hello.txt
-
-
Supply a multi-line string (including a trailing line feed) as standard input to the current command
-
Input string:
1234 5678
-
Command:
cat <<< $'1234\n5678'
-
Output:
hello world its me computer
-
-
Supply a multi-line string (excluding a trailing line feed) as standard input to the current command
cat =(<<<$'hello world\nits me\ncomputer')
-
here-string
with and without a trailing newline (using tmp file substitution)-
With trailing newline
# A simple string cat < <(<<<hello) # The output of a command cat < <(<<<$(print -n 'hello'))
-
Without trailing newline
# A simple string cat < =(<<<hello) # The output of a command cat < =(<<<$(print -n 'hello'))
-
Expanding Parameters in Files
If you have a super long string of text, for instance, a SQL query, you may want to save the contents of that query in a different file. It's possible you may need to store a variable in the query, and if so, you can use the (e)
paramater expansion flag when referencing the string. This flag causes the string to have any ${variable}
words treated as if they were a normal shell variable, and not text from the file.
-
Expanding parameters as if they were variables in a file:
# (file.txt) ''' Hello, my name is $USER and my favorite directory is $HOME ''' info=${(e)$(<./file.txt)} print ${info} # [ Output ] # ========== # => Hello, my name is austin # => and my favorite directory is /Users/austin
exit
vs. logout
-
exit
will close all shells, interactive, non-interactive, login, non-login -
logout
will only close out of a login shell, even if it's interactive -
return
will stop the execution of the script that made the call, butexit
will close the shell that sourced that file to begin with
Brace Expansion
-
Multiple mid-word character substitutions
print h{a,e,i,o,u}p # => hap hep hip hop hup
-
Repeating a string multiple times
print 'woah'{,} # woah woah print 'woah'{,,} # woah woah woah print 'woah'{,,,} # woah woah woah woah
-
Back-to-back expansions with filename generation
print -- {/,/usr/}{bin/*,sbin/*}
-
Generating ranges of numbers
print {01..10}
01 02 03 04 05 06 07 08 09 10
print {01..10..3}
01 04 07 10
print {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
print {a..z..3}
a d g j m p s v y
left=1 right=9 print {${left}..${right}}
1 2 3 4 5 6 7 8 9
Brace expansion can be used in powerful ways, namely to be lazy, the most powerful force in the universe.
-
Changing a file's extension a file
mv ${
-
Unzipping a file into a directory of the same name
unzip {,-d\ }./FILENAME
unzip ./FILENAME -d ./FILENAME
The ternary operator
Ternary operators are supported in Zsh, but only when they are used within an arithmetic evaluation, such as (( a > b ? yes : no ))
a=5
b=6
max=$(( a > b ? a : b ))
print "The max is ${max}"
The max is 6
[[ "apple" < "banana" ]] && print "yes" || print "no"
# => "yes"
[[ 1 -eq 1 ]] && asdf || print "Not true"
bash: asdf: command not found Not true
[[ 1 == 1 ]] && { asdf ;:; } || print "Not true"
"bash: asdf: command not found"
ANSI C Quotations
-
Print two lines using C quotes
$'...'
print $'2\nlines'
2 lines
-
Print the character corresponding with the hex value
0x41
print $'\x41'
A
-
Print the character corresponding with the UTF-8 character code
u+7231
print $'\u7231'
爱
-
Print the character corresponding with the UTF-8 character code
U+1f602
print $'\U0001f602'
😂
Regular Expressions
The zsh/regex
module handles regular expressions. There's support for PCRE regular expressions, but by default regular expressions are assumed to be in Extended POSIX form.
You can use the =~
operator to test a value against a pattern
pie=good
[[ $pie =~ d ]] && print 'Match found'
[[ $pie =~ [aeiou]d ]] && print 'Match found'
# No match because the regular expression has to capture the value of
# the variable, not the variable itself
[[ $pie =~ [p][i]e ]] || print 'No match found'
# No match because there's no literal '[aeoiu]d' inside the word "good"
[[ $pie =~ "[aeiou]d" ]] || print 'No match found'
The value of the match is saved to the MATCH
variable. If you used capture
On successful match, matched portion of the string will normally be placed in
the MATCH
variable. If there are any capturing parentheses within the regex,
then the match
array variable will contain those. If the match is not
successful, then the variables will not be altered.
if [[ 'My phone number is 123-456-7890' =~ '([0-9]{3})-([0-9]{3})-([0-9]{4})' ]] {
typeset -p1 MATCH match
}
typeset MATCH=123-456-7890 typeset -a match=( 123 456 7890 )
Arithmetic Evaluation
a=2
b=4
print $((a*b)) # => 8
# You can even do assignments. The last value calculated will be the output.
b=$(( a *= 2 ))
print "b=$b a=$a"
# b=4 a=4
Floating Point Arithmetic
a=$(( 1 + 1 ))
message="I don't want to brag, but I have like $(( a + 1 )) friends."
print $message
I don't want to brag, but I have like 3 friends.
print "6 / 8 = $(( 6 / 8 ))"
6 / 8 = 0
print "6 / 8 = $(( 6 / 8.0 ))"
6 / 8 = 0.75
File Descriptors
-
<&-
: Close the standard input. -
1>&-
: Close the standard output. -
2>&-
: Close the standard error. -
<&p
: Move the input from the coprocess to stdin -
>&p
: Move the output from the coprocess to output -
2>&1
: Redirect standard error to standard output -
1>&2
: Redirect standard output to standard error -
&> file.txt
: Redirect both standard output and standard error tofile.txt
-
Redirect output and error to different files
func() { print 'output' >&1 print 'error' >&2 } # [ Version 1 ] func 1>out.txt 2>err.txt # [ Version 2 ] 1> out.txt 2> err.txt func
Custom File Descriptor
You can create your own file descriptor number and have it direct to any file you'd like.
- Create file descriptor
3
and point it to/dev/null
exec 3> ~/three.txt
print 'one' >&1
print 'two' >&2
print 'three' >&3
exec 3>&-
exec {four}>&-
# Open file descriptor 3, Direct output to this file descriptor
# toward the file ~/three.txt
exec 3> ~/three.txt
# Open file descriptor allocated by shell to unused
# file descriptor >= 10. Direct output to this file descriptor
# toward the file ~/fd.txt
exec {fd}> ~/fd.txt
# (alternative: sysopen -w -u 3 /dev/null)
shout() {
print 'File descriptor 1' >&1
print 'File descriptor 2' >&2
print 'File descriptor 3' >&3
print 'File descriptor fd' >&$fd
}
shout
# => (1:) 'File descriptor 1'
# => (2:) 'File descriptor 2'
# Close file descriptor 3
exec 3>&-
# Close file descriptor fd
exec {fd}>&-
shout
# => (1:) 'File descriptor 1'
# => (2:) 'File descriptor 2'
# => (3:) 'error: bad file descriptor'
# => (12:) 'error: bad file descriptor'
Technically this is a little dangerous, especially for file descriptors 3-8, (for instance, #5 is used when spawning child processes), so it's best to do the alternative "variable name" method, shown below
-
Create a file descriptor variable named
fd
and have the shell assign a free file descriptor (starting from 10+) and save it as the value of that variable -
{abc}>&1
: create a file descriptor "abc" that is a duplicate of file descriptor 1 -
>&$abc
: write to the file descriptor "abc" -
<&$abc
: read from the file descriptor "abc" -
{abc}>&-
: close file descriptor "abc"
# Open file descriptor `fd` that redirects to 'output.txt'
exec {fd}> ~/output.txt
print "{fd} points to file descriptor ${fd}"
# => "{fd} points to file descriptor 12"
print $'alpha\nbravo\ncharlie' >&$fd
# Close file descriptor 'fd'
exec {fd}>&−
print $'alpha\nbravo\ncharlie' >&$fd
Disowning a Job
If you have a command that you'd like to continue running, even after the shell
has been closed, you can use the disown
builtin command. There is an explicit
syntax, and a short-hand syntax.
-
Disowning the job process id
%1
# Explicit syntax disown %1
# Shorthand syntax %1&|
Delete Dangling Symlinks
Sometimes symbolic links point to files that don't exist, it's useful to delete them, and zsh
makes that super simple by using glob qualifiers.
-
(@)
: Only symlinks -
(-@)
: Only broken symlinks -
(D)
: Match.hidden
dot files -
Deleting all dangling symlinks:
# '-@' only broken symlinks # 'D' include .dotfiles rm -- ./*(-@D)
Remove Element From Array
Sometimes you have an array of elements, and you need to remove a value from the array, but you don't know the index that this value is located at.
-
Removing an element from an array
# Array with four elements array=(alpha bravo charlie delta) # Getting the index of 'charlie' print ${array[(i)charlie]} # => "3" # Removing an element at an index (without leaving it as an empty slot) ${array[3]}=() # Doing it in one line array[${array[(i)charlie]}]=()
-
Removing an element from an array
# Array with four elements array=(alpha bravo charlie delta) # Array 'filter' with elements 'bravo' and 'charlie' filter=(bravo charlie) # Remove from 'array' any element contained in the array 'filter' excluded=(${array:|filter}) # (alpha delta) # Remove from 'array' any element *not* contained in the array 'filter' included=(${array:*filter}) # (bravo charlie)
You can also remove elements from an array based on patterns. This filter takes on the syntax
${array:#PATTERN}
where PATTERN is the same as the form used in
filename generation.
-
Remove from
array
any element that matches the pattern:typeset -a output=( 'number one' three )# *r*: strings containing the letter 'r' array=('number one' two three) output=(${array:#*w*}) typeset -p output
-
Remove from 'array' any element that does not match the pattern
pattern\*
typeset -a output=( two )# *r*: strings containing the letter 'w' array=('number one' two three) output=(${(M)array:#*w*}) typeset -p output
-
Remove any line from 'whois' that doesn't start with 'CIDR'
CIDR: 8.0.0.0/9ip='8.8.8.8' print -- ${(M)${(@)${(f)${"$(whois ${ip})"}}}:#CIDR*}
Background Jobs
-
Put a job in the background upon launch
# Receive stdout ./executable arg1 arg2 & # Do not receive stdout ./executable arg1 arg2 > /dev/null & # Immediately disown the process, but still receive stdout and stderr ./executable arg1 arg2 &! # Immediately disown the process, receive neither stdout nor stderr ./executable arg1 arg2 &> /dev/null &!
Checking For a Command
The commands
variable in zsh
is an associative array whose keys are all of the commands that can be used, and whose values are the corresponding filepaths to where those commands are located. The +
operator when applied to an associative array will have the variable expand to 1
if the key is found, and 0
if the key is not found.
-
Check if a command is not available
if (( ! ${+commands[gpg]} )); then print "Command not found" else print "Command was found" fi
Parsing Command Options
The zparseopts
module can be used to create a function or program that can accept command-line options. For more information about how to use it, you can search for zparseopts
in man 1 zshmodules
Attached below you will see a wrapper I wrote for the transmission
command line interface, as there is no way to cleanly alias the transmission
command without writing a wrapper like this, as it installs as five separate commands.
# Parse the following command-line arguments as options
# Note on `-a`:
# Specify the name of where to save parsed options
# In this case, into an array named `option`
# Note on `-D`:
# if an option is detected, remove it from
# the positional parameters of the calling shell
zparseopts -D -a option \
'c' '-create' \
'd' '-daemon' \
'e' '-edit' \
'r' '-remote' \
'q' '-quit'
case ${option[1]} in
-c | --create)
transmission-create ${@}
;;
-d | --daemon)
transmission-daemon ${@}
;;
-e | --edit)
transmission-edit ${@}
;;
-r | --remote)
transmission-remote ${@}
;;
-q | --quit)
transmission-remote --exit
exit 0
esac
typeset
The typeset
builtin declares the type of a variable identified by a
name that is optionally assigned a value value.
When an assignment is not made, the value of name is printed
as follows:
typeset -i a=1
a+=1
typeset a
Output
a=1
typeset b=1
typeset b=1
Flags to state variable type
-F [ name[=value] ... ]
: set name as floating point (decimal notation)-E [ name[=value] ... ]
: set name as floating point (scientific notation)-i [ name[=value] ... ]
: set name as an integer-a [ name[=value] ... ]
: set name as an array-A [ name[=value] ... ]
: set name as an associative array
Flags to state variable properties
typeset -r [ name[=value] ... ]
: mark name as read-onlytypeset +r [ name[=value] ... ]
: remove the read-only property of NAMEtypeset -x [ name[=value] ... ]
: mark name as exportedtypeset -g [ name[=value] ... ]
: mark name as globaltypeset -U [ name[=value] ... ]
: convert array-type variable name such that it always contains unique-elements only
Flags to modify command output
-
typeset -l [ name[=value] ... ]
: print value of name in lower-case whenever expanded -
typeset -u [ name[=value] ... ]
: print value of name in upper-case whenever expanded -
typeset -H [ name[=value] ... ]
: suppress output fortypeset name
if variable name has already been assigned a value -
typeset -p [ name[=value] ... ]
: print name in the form of a typeset command with an assignment, regardless of other flags and options. Note: the−H
flag will still be respected; no value will be shown for these parameters. -
typeset -p1 [ name[=value] ... ]
: print name in the form of a typeset command with an assignment, regardless of other flags and options. Note: arrays and associative arrays are printed with newlines between indented elements for readability.
Matching a Certain Type
-
Printing all variables of a certain
typeset
:# All variables with their types typeset + # All variables that are floating point typeset -E + # View the assignment of a variable typeset USER
Matching a Certain Pattern
- Print environment variables whose names match the pattern
typeset +m 'foo*'
foo foo_fighters food
- Print variable and its corresponding value for environment variables whose names match the pattern
typeset -m 'foo*'
foo=bar foo_fighters=awesome food=(my life)
- Print variables'
typeset
options, its name, and its assigned value, for each matching the pattern:
typeset -p -m 'foo*'
typeset foo=bar typeset foo_fighters=awesome typeset -a food=( my life )
-
Print all keys in an associative array that don't start with
foo
print ${(k)example:#foo*}
-
Print all keys in an associative array that do start with
foo
print ${(Mk)example:#foo*}
-
Print all values in an associative array that don't start with
foo
print ${(v)example:#foo*}
-
Print all values in an associative array that do start with
foo
print ${(Mv)example:#foo*}
Pairing Scalars and Arrays
If you're using a shell scripting language, you often have to export directories to the environment, such as for PATH
, which requires a list of directories separated by a colon.
Zsh gives you the ability to link two variables together, a scalar and an array. You can specify the delimiter that separates elements, and once you have, adding items to the array will add items to the scalar. An example is provided below:
-
Linking a scalar and an array
typeset -T COLORS colors ':' colors=(red) colors+=(blue green) print ${COLORS} # => "red:blue:green"
Printing Colors
Printing colors can be done with SGR escape codes, explained on the ASCII page, but you can also do it with the prompt string format specifier syntax outlined below:
For each of the following examples, we'll format the scalar text
text="Hello world"
-
Set the foreground color of
text
to redprint -P "%F{1}${text}%f"
Hello world
-
Set the background color of
text
to blueprint -P "%K{blue}${text}%k"
Hello world
-
Format
text
to be underlinedprint -P "%U${text}%u"
Hello world
-
Format
text
to be boldedprint -P "%B${text}%b"
Hello world
Additionally, you can use %S
for standout formatting, which
swaps the foreground and background colors.
Custom Keybindings
Use the zle
module for binding custom keys, code written using zle
can be
sourced in your configuration files.
-
Enabling vi-mode
bindkey -v
-
Creating custom key-binding using
zle
module:custom-command() { print -n 'Hello world' zle accept-line } # Register this command with the Zsh Line Editor zle -N custom-command # Bind this key to <Option-Shift-S> in Zsh vi-mode bindkey -M viins '\ES' custom-command
-
Binding the
<Return>
key or<C-m>
to not execute the current line# in Zsh bindkey -M viins '\C-m' self-insert-unmeta
Completions
compsys
-
The new system to use is
compsys
. It has a manpagezshcompsys(1)
as well. -
The old system was called
compctl
and its manpage is inzshcompctl(1)
. The first paragraph is dedicated to recommending you just head back over to the newzshcompsys(1)
system.
Completion Functions
Let's say our program is called hello
.
Here's what will happen:
- You write a completion function, typically
_<cmd-name>
_hello() {
# You write your code here
}
- You bind your function to a command
compdef _hello hello
- Whenever you press
<Tab>
afterhello
,_hello
will be called.
Whenever you want to throw out possible completions, you'll use one of the following utility functions(in this post):
compadd
- Reference:
man zshcompwid
If you want to have this:
hello <Tab>
# => cmd1 cmd2 cmd3
You'll write this:
comdadd cmd1 cmd2 cmd3
_describe
If you want to have this:
hello <Tab>
# => cmd1 -- description1
# => cmd2 -- description2
You'll write this:
_describe 'command' "('cmd1:description1' 'cmd2:description2')"
Note: In both of above commands, we didn't consider which argument no. it is, means even hello cmd1 <Tab>
will give same output. Next command will solve this problem.
_arguments
Now this is a powerful one. You can control multiple arguments.
By multiple arguments I mean hello arg1 arg2
not hello arg1|arg2
Here's the basic syntax: _arguments <something> <something> ...
where <something>
can either be:
'-o[description]'
for an option'<argument number>:<message>:<what to do>'
for an argument
First one is self-explanatory, whenever called it'll output the description:
hello <Tab>
-o -- description
For the second one, <argument number>
is self-explanatory. I'll leave message
empty to demonstrate a minimal example. For <what to do>
, it can be quite a few things, two of which are provided below:
-
List of arguments possible at given
argument number
. For example, if two arguments(world
anduniverse
) are possible at argument one(hello world|universe
), we can write:_arguments '1: :(world universe)' <something> ...
-
Set variable
state
to an identifier. For example, if we want to call another function at argument no. 2, we can write:typeset state _arguments '2: :->identifier' case ${state} in identifier) #do some special work when we want completion for 2nd argument ;; esac
That might be confusing, lets sum up _arguments
by an example:
Lets say, our program has possible args like:
hello [cat|head] <file at /var/log> one|two
Its completion function can be:
_hello() {
local state
_arguments '1: :(cat head)' '2: :->log' '3: :->cache'
case ${state} in
log)
# This is for demonstration purpose only, you'll use _files utility to list a directories
_describe 'command' "($(ls $1))"
;;
cache)
# This could be done above also, in _arguments, you know how :)
compadd one two
;;
esac
}
Job Control
There are several ways to refer to jobs in the shell. A job can be referred to by the process ID of any process of the job or by one of the following:
-
%2
The last job with job ID2
-
%vi
The last job whose command line begins withvi
-
%?grep
The last job whose command line containsgrep
-
%%
The current job. -
%+
Equivalent to%%
. -
%-
The previous job.
Zsh Modules
Zsh comes with many useful modules, but none are loaded by default. This is done in order to prevent optional features from needlessly slowing down the shell's startup time.
zsh/nearcolor
-
Print the closest match to violet (
#AFAFFF
) among the 256 terminal colorszmodload zsh/nearcolor print -P '%F{#AFAFFF}Violet%f`
Multios
- See output on
stdout
but save tofile.txt
as well
date >&1 >file
Operating System Commands
There are some ANSI escape sequences that allow you to write Operating System Commands (OSCs)
-
Set the title of the terminal tab to
TAB
# The `1` specifies to change the tab title print '\x1b]1;TAB\x07'
-
Set the title of the terminal window to
WINDOW
# The `2` specifies to change the window title print '\x1b]2;WINDOW\x07'
Default Zsh Options
Included below, more for my reference, but could be helpful for anyone
# Print an error if a glob pattern is badly formed
setopt BAD_PATTERN
# Print an error if a glob pattern does not match any files
setopt NOMATCH
# Treat unset parameters as '' in subs, 0 in math, otherwise error
setopt UNSET
# Consider parentheses trailing a glob as qualifiers
setopt BARE_GLOB_QUAL
# Match regular expressions in `=~` case sensitively
setopt CASE_MATCH
# Perform =file expansion
setopt EQUALS
# Perform history expansion with `!`
setopt BANG_HIST
# Calling `typeset -x` implicitly calls `typeset -g -x`
setopt GLOBAL_EXPORT
# Allows a short-form syntax for `if`, `while`, `for`, etc.
setopt SHORT_LOOPS
# Run all background jobs at a lower priority
setopt BG_NICE
# Report the status of background jobs (typically it isn't done until <CR>)
setopt NOTIFY
# Confirm before logoff w/ background/suspended jobs
setopt CHECK_JOBS
setopt CHECK_RUNNING_JOBS
# Send the HUP signal to running jobs when the shell exits
setopt HUP
# Treat '%' specially in prompt strings
setopt PROMPT_PERCENT
# Set $0 equal to name of script for funcs & script
setopt FUNCTION_ARGZERO
Short Form Syntax
Zsh supports the traditional syntax for conditional statements and for loops. However, they also provide some more modern versions of each, as demonstrated below:
-
One line
if
statement, single command:if [[ ${USER} == 'austin' ]] print "That's him"
-
Multi-line
if
statement, any number of commands:if [[ ${USER} == 'austin' ]] { print "That's him" } elif [[ ${USER} == 'valerie' ]] print "That's her" } else { print "That's nobody important" }
-
Multi-line
for
loop, any number of statementswords=('one' 'two' 'three' ) for word in ${words}; { print ${word} }
-
Syntax for short-form of
while
loop# Keep sleeping until the server starts running while [[ $(curl http://127.0.0.1 -- &> /dev/null)$? -eq 7 ]] { sleep 0.2 } print "Server is now running"
Silent Functions
You can specify that a function can be silent in its declaration! If you know you're going to make a helper function that you don't want to ever see output from, you can define it using the syntax outlined in the example below:
-
Create a silent function
func() { print 'Never seen' return 0 } &> /dev/null
Zsh Time Profiling
zmodload zsh/zprof
# Start up functions in ~/.zshrc
zprof
calls time self name
- 2 22.18 11.09 45.03% 22.18 11.09 45.03% compaudit
- 1 32.66 32.66 66.29% 10.48 10.48 21.27% compinit
- 5 0.77 0.15 1.56% 0.77 0.15 1.56% add-zsh-hook
- 1 0.45 0.45 0.90% 0.45 0.45 0.90% bashcompinit
- 1 0.28 0.28 0.56% 0.28 0.28 0.56% is-at-least
- 1 0.15 0.15 0.31% 0.15 0.15 0.31% (anon)
- 1 0.09 0.09 0.19% 0.09 0.09 0.19% compdef
- 1 0.18 0.18 0.37% 0.09 0.09 0.18% complete
Zsh Completion Audit
To fix any ownership problems experienced during zsh completion, you can run the script below
for line in $(compaudit &>1); do
if [[ -e ${line} ]]; then
sudo chown ${UID}:${GID} ${line}
sudo chmod -v 755 ${line}
fi
end
Pretty-Printing Associative Array
-
Print the key-value pairs found in
my_pairs
typeset -p1 my_pairs
typeset -A my_pairs=( key1=val1 key2=val2 key3=val3 )
Zsh Hashed Commands
Instead of searching the path each time for a command, Zsh hashes commands
hash
-
Rebuild the hash table for commands found in the user
path
:hash -f
enable
-
enable a builtin command
enable whoami
-
enable an alias
enable -a lsa
-
enable a function
enable -f func
disable
-
Disable a builtin command
disable whoami
-
Disable an alias
disable -a lsa
-
Disable a function
disable -f func
unhash
You can use the unhash
tool to remove almost any type of command from your current shell.
-
Remove a command
unhash whoami
-
Remove an alias
unhash -a lsa
-
Remove a function
unhash -f func
Terminal
Below are some messy notes from a previous page I had dedicated to terminals, which, for the time being, is being placed here as a dedicated terminal page is difficult to expand upon when there's also a dedicated shell scripting page.
Navigating The Terminal
Common Movement Shortcuts
Shortcut | Output |
---|---|
⌃ A | Go to the beginning of the line |
⌃ E | Go to the end of the line |
⌥ F | Move forward one word |
⌥ B | Move back one word |
Clearing Text
Shortcut | Output |
---|---|
⌘ K | Erase the entire terminal |
⌘ L | Erase the last command's terminal output |
Modifying Chars
Shortcut | Output |
---|---|
⌃ F | Move forward 1 char |
⌃ B | Move backward 1 char |
⌃ H | Delete char left of cursor |
⌃ D | Delete char right of cursor |
⌃ T | Swap the last two chars |
Modifying Words
Shortcut | Output |
---|---|
⌥ L | lowercase word right of cursor |
⌥ U | uppercase word right of cursor |
⌥ C | title-case word of cursor |
⌃ Y | Paste the word that was deleted |
⌥ T | Push the word left of the cursor forward by one word |
Modifying Lines
Shortcut | Output |
---|---|
⌃ K | Erase line right of cursor |
⌃ U | Erase line left of cursor |
⌃ W | Erase argument left of cursor |
⌃ Y | Paste what was just erased |
⌃ A | Go to the beginning of the line |
⌃ E | Go to the end of the line |
Undo Action
Shortcut | Output |
---|---|
⌃ - | Undo last keystroke |
Command Selection
Shortcut | Output |
---|---|
⌃ P | Select previous command |
⌃ N | Select next command |
⌃ R (1) | Recall a previous command |
⌃ R (2) | Recall the next match |
⌃ G | Exit from command recall mode |
⌥ R | Restore altered command back to it's original state |
⌃ J | Submit command |
Completion Shortcuts
There are a bunch of shortcuts that will help you complete the filename, or the command name, etc., but let's be real here. You're just going to keep using tab
anyway. Save your energy for learning some of the other great shortcuts on here.
Misc Input
Many of the keys you normally press can be entered with a control key combo instead.
Shortcut | Output |
---|---|
⌃ I | tab |
⌃ J | newline |
⌃ M | enter |
⌃ [ | escape |
⌃ D | $ exit closes the entire terminal session |
Shortcut | Output |
---|---|
⌃ < | Go to beginning of history |
⌃ > | Go to end of history |
Signals
Ranked from weakest to strongest
Shortcut | Output | Signal | Number | Notes |
---|---|---|---|---|
⌃ Z (1) | Pause a job | SIGTSTP | 20 | Also known as suspending a job |
⌃ Z (2) | Continue a job | SIGCONT | 18 | Pressing ⌃Z again will continue a process that was just suspended |
^ C | Interrupt a job | SIGINT | 2 | Tell a process that it should not continue, the most common way to end a program |
⌃ \ | Quit a job | SIGQUIT | 3 | Similar to an interrupt, but a little stronger (can still be caught), and will produce a core dump. The strongest of the signals that can be called via keyboard shortcuts |
Signals
Various signals can be sent in UNIX to interact with a program. Many of these contain keyboard shortcuts, but first it is important to go over the most common types of signals. Programs can customize how they react to signals by catching, handling, or ignoring them.
To view all signals, type $ trap -l
To view all signal keyboard shortcuts, type $ stty -e
or $ stty all
Signal Definitions
-
SIGTERM (15): Tells a program to stop, in order to allow the program to handle its termination gracefully. Can be caught, handled, or ignored.
-
SIGINT (2): Used to interrupt a program that is running. It is the same as the SIGTERM signal, but it explicitly refers to an interruption that was called from the terminal. Can be caught, handled, or ignored.
-
SIGQUIT (3): Similar to SIGTERM but it will generate a core dump. Can be caught, handled, or ignored.
-
SIGSTOP (17): Temporarily stop a program. Cannot be caught, handled, or ignored.
-
SIGTSTP (18): Sends the program a signal, telling it to temporarily stop. Unlike SIGSTOP, it can be caught, handled, or ignored.
The Foreground & Background
The jobs
Program
The jobs
program lets you see information about the current jobs running from this terminal.
-
View the jobID, job status, and call-command
jobs
-
Additionally report the PID of each job
jobs -l
If you begin running a process, but it looks like it will take a long time to run, there's no need to open a new terminal tab. Instead, you can run the current process in the background. First, suspend (pause) the job with ⌃Z
If you have suspended multiple jobs, you can bring a specific job back to the foreground/background as follows
-
Resume the 2nd suspended job as a foreground process
fg %2
-
Resume the 3rd suspended job as a background process
bg %3
The ps
Program
-
View info about all active processes
ps
The pgrep
Program
To find out the process ID of a particular program, use the pgrep
program.
-
View the PID of all matches to the regular expression "java"
pgrep java
-
View the id and name of every process matching the regular expression "ja"
pgrep -l ja
Managing active processes
Every process has a process ID or "PID" and there are a variety of commands that you can use to manage your active processes.
-
Find an active process's PID by name
pgrep <process_name>
The kill
Program
Using the kill
program, you can send any active process a signal.
-
Kill a processes by PID
kill -9 <process_id>
-
Kill a process by name
pkill "java"
-
Kill a process running on a specific port
kill $(lsof -t -i :4000)
-
Send the SIGTERM (15) to process 123
kill -15 123
-
Send the SIGTERM (15) signal to process 123 & process 456
kill -TERM 123 456
-
Send the SIGINT (2) signal to process 123
kill -2 123
-
Send the SIGSTOP () signal to process 123
kill -TSTP 123
-
Send the SIGINT (2) signal to job ID # 1
kill -2 %1
The pkill
Program
Similar to kill
except instead of killing processes by id, it kills processes by name.
# [Send the SIGTERM signal to all programs matching "java"]
pkill -15 java
# [Send the SIGTSTP signal to all programs named exactly "java"]
pkill -TSTP -x java
Managing Disk Space
The df
Program
The df
program, can be used to "display free" storage available on the computer.
# Get a report of the last recorded amount of memory
$ df -kh
# Refresh this value
$ du -chs
Useful df
flags
-k
Use 1 kilobyte as the default size-unit, instead of half a kilobyte (not sure why this isn't standard...)-h
Print the response in human readable output.-c
-s
Customization
Custom Bash Prompt
The bash prompt is actually a collection of several prompts.
-
PS1: The primary bash prompt, defaults to include the following bash escape sequences.
\h
: The hostnameAustins-Macbook-Pro
\W
: The basename of the current working directory~
\u
: The usernameaustintraver
\$
: A$
char, unless the UID is not 0, then it's#
Personally I like the way it looks when I
ssh
into my virtual private server. If you want to try it out, you can run the following command in your terminal. -
Modify the machine's hostname (on macOS):
sudo scutil --set HostName HOSTNAME
Directory Structure
$PATH
When you type the name of a function on the command line, it usually requires that you tell it the language and the directory. (e.g. $ python3 greet.py
)
However, if the executable file is located in one of the directories specified by your $PATH
, then it will automatically find and run the program without you needing to provide those specifications. It searches every directory specified in your PATH
and runs the first file it finds with a matching name.
Seeing which directories are in your $PATH
# This one only works on zsh
print -l ${path}
# This one works on bash as well
echo -e ${PATH//:/\\n}
Using #!
the "hashbang"
Sometimes you open up a file and it contains the first line, or something similar, to the one I've written below in a program called greet
that prints Hello world!
greet
#!/usr/local/bin/python3
print("hello world")
That first line uses a hashbang. What it does, is it tells your computer what program to use when trying to run the code specified in the lines below. In this case, it says to use the python3
program located in the directory /usr/local/bin
Assuming this was a file in your present working directory with executable permissions (if it isn't, type $ chmod +x greet
in your terminal) then you could type $ ./greet
and this file would run fine. You didn't need to specify that it needed to run with $ python3 greet
# [Hard way]
/usr/local/bin/python3 greet
# [Medium way]
python3 greet
# [Easy way]
./greet
Typical $PATH directories
The root
directories
Note that /
itself is the root directory, these are directories inside the root
directory
The /bin
directories
These are programs that are needed if you start the system with single user mode. Single user mode is a startup mode even more barebones than recovery mode.
The /local
directories
/usr/local/bin
This is for programs that are local to your user account. If you install a program here (and you should), then the other accounts on the computer won't be able to use it. Also, it's automatically in your ${path}
/usr/local/sbin
This is the local system bin, which is used for programs that are needed to boot the system, but that you won't be executing directly.
The command path
If you want to add a directory to ${path}
you'll need to edit your ~/.zshrc
. To add the directory /Users/tommytrojan/programs
to your path, you would add the following line.
-
Add a folder of executable programs to the command path
path=(~/tommytrojan/programs ${path})
This will append /Users/tommytrojan/programs
to the existing value of ${path}
which is accessed by typing ${path}
.
The export
keyword
We used the export
keyword when we updated the $PATH in our .zshrc
but it's important to understand what it does. The export
keyword will save a variable in the shell, but it will also save the variable in any sub-shells. That means if you called a function from your terminal, and that function checked for a variable $(PATH)
it would still "remember" what that variable's value was set to be.
The Root User
On UNIX systems, the root user has capabilities that are disabled when you are logged in as a regular user. Type the command below to run a shell as the root user
sudo -i
From here, you can type any command without having to use the sudo command.
Opening applications
on MacOS
But there are very useful flags you can use, to type these out in the future
-
Open the Postman.app file explicitly
open ~/Applications/Postman
-
Open the application "Postman"
open -a Postman
-
Open a website in Safari
open -a Safari 'https://google.com'
-
Open with the default text editor
open -t textfile.txt
-
Launch a new instance of the application
open -n sample.png
on Linux
Opening an application on Linux is as easy as typing
# [Launch any application located in $PATH]
appname
The Welcome Message
Silencing the Welcome Message
Usually when you open your mac, you'll see a message such as
"Last login: Fri May 3 21:14:20 on ttys000"
But you can disable this message by adding a .hushlogin
file to your home directory.
# [Silence the login message]
touch ~/.hushlogin
Alternatively, you can customize the message by modifying the contents of the file located in /etc/motd
Hidden Programs
On Mac OS, there are some really cool hidden programs that most people don't know about.
caffeinate
Many people don't know about caffeinate
, a program you can use to prevent your computer from falling asleep.
Wake up a sleeping remote computer with ssh
# For a moment
ssh tommy@remote.net 'caffeinate -u -t 1'
Useful Flags
- The
-t
flag specifies how many seconds to stay awake - The
-w
flag will wait until the program with the given PID finishes before reenabling sleep. - The
-u
flag will signal wake via user activity, keeping a computer awake as if someone jiggled the mouse or pressed a key.
Following Symlink Directories
Add this line to your .inputrc
so that when you type cd
and try to tab-complete to a symbolic link to a directory, it will include the trailing /
at the end.
set mark-symlinked-directories on
Advanced Tab Completion
If you are typing out a command, and you include environment variables (e.g. $PATH
) or an event designator (e.g. !!
) then you can press
echo $HOME<TAB>
echo /Users/austin
Speak from Terminal
# Speaking from terminal
say 'hello world'
# Singing from terminal
say -v 'good news' di di di di di di
Arithmetic Expansion
-
If there are between 1 and 3 arguments supplied to the function, print the number of arguments supplied to the function
func() { if (( 1 <= ${#} && ${#} <= 3 )) { print ${#} } }
Boolean Shell Builtins
-
true
is a shell builtin that returns0
when called -
false
is a shell builtin that returns1
when called
This is an example where the shell will print success
if the commands whoami
and hostname
both return status code 0
.
if whoami && hostname; then
print 'success'
fi
ttrojan challenger success
You don't have to use real commands, you could use the shell builtin true
,
which always returns status code 0
. (In fact, it's all that true
actually
does!)
if true && true; then
print 'success'
fi
success
Operator Precedence
Proof that ||
has operator precedence over &&
-
Example 1:
if true && false || true; then print 'success' else print 'failure' fi
success
-
Example 2:
if true || false && true; then print 'success' else print 'failure' fi
success
zmv
The zmv
command is an alternative to mv
, and can be loaded into the shell
environment using the autoload
command.
-
Loading the
zmv
command:autoload zmv
Usage
-
Rename a section of a filename, i. e.
example.1.{txt,conf,db}
or12345.1.{wav,ogg,mp3}
and change the1
to a2
in the filename:# would rename x.0001.y to x.2.y. zmv -n '(*.)(<->)(.[^.]#)' '$1$(($2+1))$3' zmv -n '(*.0#)(<->)(.[^.]#)' '$1$(($2+1))$3'
-
Change files to lowercase:
zmv '*' '${(L)f}'
-
Serially rename all files (e.g.:
foo.foo
->1.foo
,fnord.foo
->2.foo
, etc.):ls * # 1.c asd.foo bla.foo fnord.foo foo.fnord foo.foo c=1 zmv '*.foo' '$((c++)).foo' ls * # 1.c 1.foo 2.foo 3.foo 4.foo foo.fnord
-
Rename
file.with.many.dots.txt
by substituting dots (except for the last one!) with a single space:touch {1..20}-file.with.many.dots.txt zmv '(*.*)(.*)' '${1//./ }$2'
-
Remove the first 4 chars from a filename
zmv -n '*' '$f[5,-1]' # NOTE: The "5" is NOT a mistake in writing!
-
Rename names of all files under the current directory to lowercase, but keep the directory names themselves intact.
zmv -Qv '(**/)(*)(.D)' '$1${(L)2}'
-
Replace all 4th character, which is "1", with "2" and so on:
autoload -U zmv zmv '(???)1(???[1-4].txt)' '${1}2${2}'
-
Remove the first 15 characters from each filename:
touch 111111111111111{a-z} zmv '*' '$f[16,-1]'
-
Replace spaces (any number of them) with a single dash in filenames:
zmv -n '(**/)(* *)' '$1${2//( #-## #| ##)/-}'
-
Clean up filenames and remove special characters:
zmv -n '(**/)(*)' '$1${2//[^A-Za-z0-9._]/_}'
-
Lowercase all extensions (i.e.:
*.JPG
) including those found in subdirectories:zmv '(**/)(*).(#i)jpg' '$1$2.jpg'
-
Remove leading zeros from file extension:
ls # filename.001 filename.003 filename.005 filename.007 filename.009 # filename.002 filename.004 filename.006 filename.008 filename.010 zmv '(filename.)0##(?*)' '$1$2' ls # filename.1 filename.10 filename.2 filename.3 filename.4 filename.5 # filename.6 ...
-
Renumber files:
ls * # foo_10.jpg foo_2.jpg foo_3.jpg foo_4.jpg ... zmv -fQ 'foo_(<0->).jpg(.nOn)' 'foo_$(($1 + 1)).jpg' ls * # foo_10.jpg foo_11.jpg foo_3.jpg foo_4.jpg ...
-
Adding leading zeros to a filename:
# 1.jpg -> 001.jpg, ... zmv '(<1->).jpg' '${(l:3::0:)1}.jpg'
-
Add leading zeroes to files with a filename with at least 30 characters:
typeset c=1 zmv "${(l:30-4::?:)}*.foo" '$((c++)).foo'
-
Replace all spaces within filenames into underlines:
zmv '* *' '$f:gs/ /_'
-
Change the suffix from
*.sh
to*.pl
:zmv -W '*.sh' '*.pl'
-
Add a
.txt
extension to all the files within${HOME}
:zmv -Q '/home/**/*(D-.)' '$f.txt'
-.
is to only rename regular files or symlinks to regular filesD
is to also rename hidden files (files with names that start with.
)
-
Only rename files that don't have an extension:
zmv -Q './**/^?*.*(D-.)' '$f.txt'
-
Recursively change filenames with characters contained in the character set
[?=+<>;",*-]'
:chars='[][?=+<>;",*-]' zmv '(**/)(*)' '$1${2//$~chars/%}'
-
Removing single quote from filenames (recursively):
zmv -Q "(**/)(*'*)(D)" "\$1\${2//'/}"
-
When a new file arrives (named file.txt) rename all files in order. For example,
file119.txt
becomesfile120.txt
,file118.txt
becomesfile119.txt
and so on ending withfile.txt
being changed to becomefile1.txt
:zmv -fQ 'file([0-9]##).txt(On)' 'file$(($1 + 1)).txt'
-
Convert all filenames to be entirely lowercase:
zmv '(*)' '${(L)1}'
-
Convert all filenames to be entirely uppercase:
zmv '(*)' '${(U)1}'
-
Remove the suffix
*.sh
from all shell script files:zmv '(*).sh' '$1'
-
Uppercase only the first letter of all
*.mp3
files:zmv '([a-z])(*).mp3' '${(C)1}$2.mp3'
-
Copy the target
README.md
file into same directory as eachMakefile
:zmv -C '(**/)Makefile' '${1}README.md'
-
Removing the single quotation character
'
from filenames:zmv -Q "(*'*)(D)" "\$1\${2//'/}"
-
Rename
pic1.jpg
,pic2.jpg
, etc., intopic0001.jpg
,pic0002.jpg
, etc.:zmv 'pic(*).jpg' 'pic${(l:4::0:)1}.jpg' zmv 'pic(*).jpg' '$1/pic${(l:4::0:)2}.jpg'