I'm trying to make a zip function that'll do all the steps I typically do when zipping a file or folder. Say my current directory is ~, and I want to zip a folder named Folder1, whose file path is ~/Folder2/Folder1.


cd ~/Folder2 && zip -r Folder1 Folder1 && cd -

does exactly what I want. Namely, it zips so that the output Folder1.zip does not contain folders that lead up to the folder I'm zipping (namely, /home/user/Folder2), it zips recursively, and it takes in the first argument of zip with the exact name as the file or folder I'm zipping (Folder1.zip).

So I want to make a function in my .zshrc something akin to

coolerzip() { cd /path/to/file && zip -r file file && cd - }

where it need only take in one argument, which in this case, it will be ~/Folder2/Folder1. Then Folder1 will be the name for both files in coolerzip, and I will cd right into ~/Folder2 so Folder1 lies in my current directory before zipping. Is there any way I can achieve this so that coolerzip only requires one argument rather than three?

  • Take a look at unix.stackexchange.com/questions/14705/… - maybe it will be helpful. You can make zip archives programatically with the Python zipfile module. So you might consider that unless you absolutely want to do it in shell. Commented Aug 14, 2013 at 10:55
  • Thanks for the link. Unfortunately, my question seems to be more related to a bash scripting problem (i.e., parsing an argument and taking a piece of it as your desired argument), rather than one tied into attributes in the zip format. Commented Aug 14, 2013 at 11:02

2 Answers 2


The argument of the function is $1.

You can use history expansion modifiers to extract the directory part and the last component of the path: if the argument is ~/directory2/directory1 then $1:h is ~/directory2 and $1:t is directory1. (Mnemonic: head and tail.)

Use parentheses instead of braces around the function body. This way the function body runs in a subshell (a separate shell process), and variable assignments, directory changes and so on only affect the subshell.

coolerzip () (
    cd $1:h &&
    zip -r $2:h.zip $2:h

Other shells don't have history expansion modifiers, so you'd need something different to parse the argument. For the file name, ${1##*/} strips off all leading directory components; however, this doesn't work if there's a trailing slash in the parameter, whereas "$(basename -- "$1")" works in that case. For the leading directories, use $(dirname -- "$1"). Note that in shells other than zsh, you need to put double quotes around variable substitutions.

coolerzip () (
    name="$(basename -- "$1")"
    cd "$(dirname -- "$1")" &&
    zip -r "$name.zip" "$name"
  • Thanks! This solves the current directory dilemma. I couldn't get the first coolerzip() to work in zsh, but the second did fine. Commented Aug 16, 2013 at 16:38

I'm not a zsh-user, but the following works in bash and I don't think it's using any particular bashisms, so this should do the job:

coolerzip() { cd "${1%/*}" && zip -r "${1##*/}" "${1##*/}" && cd -; }

$1 is a positional parameter; in a script or a function it will reference the first argument given to the script or function on the command-line. You can also use $2 and so on, up to $9. $0 gives you the name of the script or function (the zeroth argument), $@ gives you every argument except the zeroth and $# gives you the number of arguments (again, excluding the zeroth). Things behave a bit differently if you call sh -c (or any shell) from the command-line, but I'm not going to go into that here.

${1%/*} returns the value of $1 with the shortest match of the glob pattern */ stripped away (if you want the longest match at the end, use %%) -- so it strips away the final part of the path you feed the function. ${1##*/} strips away the longest match of the pattern */ at the beginning of $1 (for the shortest match at the beginning, use a single #).

The function above is a bit fragile; it'll break if there are any trailing slashes in the argument (for example ~/folder1/folder2/). It will break in a different way if the argument is a single directory name which doesn't contain any slashes. An easy way around this is to use the readlink command to return the full path of the input argument (and also strips out any trailing slash, at least on my Ubuntu system).

coolerzip() { a="$(readlink -f "$1")"; cd "${a%/*}" && zip -r "${a##*/}" "${a##*/}" && cd -; }
  • ${1%/*} won't work when the argument happens to be a directory name in the current directory, with no /. Commented Aug 15, 2013 at 0:21
  • @Gilles that's a good point, and it will also break if there is a trailing slash, so I've updated the answer to use readlink to make it more robust.
    – evilsoup
    Commented Aug 15, 2013 at 7:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .