Make Packages from all Installed Ports in FreeBSD

Sometimes it's useful to create .tbz packages of all the installed ports on your FreeBSD system. Whether it's for a backup or for use on other similar systems, using the ports that are already built to your liking can save lots of time.

FreeBSD comes with a neat little command called "pkg_create". To use the command you specify the "-b" switch followed by the port name.

pkg_create -b apache-2.2.22_5

Moments later you'll have a new file named apache-2.2.22_5.tbz in the current directory.

For all Installed Ports

To see all your currently installed ports, use the built in "pkg_info" utility (isn't FreeBSD easy?).

pkg_info
...
apr-ipv6-devrandom-gdbm-db42-1.4.5.1.3.12_1 Apache Portability Library
arc-5.21p           Create & extract files from DOS .ARC files
arj-3.10.22_4       Open-source ARJ
autoconf-2.69       Automatically configure source code on many Un*x platforms
autoconf-wrapper-20101119 Wrapper script for GNU autoconf
automake-1.12.2     GNU Standards-compliant Makefile generator
....

If you have hundreds of installed ports, it's a little insane to type out and run the command for every one of them. The process would takes days! To create packages from all the installed ports, we'll do a little bit of shell scripting.

There is no reason to fear this shell script. It's merely a for-each-loop, that repeats a command over and over for a specific variable. Our variable in this case is the port name.

We have a way to list all our installed ports, but that list also includes a description which we don't need. The "cut" command will prove very useful here.

Just like the name says, it cuts out a section from a line. The command can be a little daunting at first, but once you understand it's most basic operation you'll find out how useful it is. For our case it requires two options: a delimiter and a field number, easily enough, the options are "-d" and "-f".

Taking an example from our port list, a line looks like this:

arc-5.21p           Create & extract files from DOS .ARC files

The port name is the first item, the description is the second. They are separated by white space. Therefore our delimiter is a " " (white space aka a single SPACE). The field number is one (1). The command would look like this:

cut -f 1 -d " "

Easy? isn't it?

We have pkg_info for getting a list of all ports with descriptions and cut -f 1 -d " " to extract just the port name from that command. Put them together and we get a list of ports. In FreeBSD we have a neat little item called a "pipe". It's that strange looking vertical line sitting on top of your ENTER key. It looks like this: "|". Piping the output to another command (as we call it in FreeBSD) is how you join two commands together: command1 | command2.

To join our pkg_info and cut commands, we do the following:

pkg_info | cut -f 1 -d " "

Press enter and you'll see a list of just the names of your installed ports.

Now, the final piece of the puzzle is slightly more involved. We need to combine pkg_info | cut -f 1 -d " " with the pkg_create command. Problem is that we can't pipe them because pkg_create won't understand what to do with a list of port names.

Here is where the for-each-loop comes into play.

for X in $LIST_O_STUFF ; do
     something_with $X
done

This will repeat the command for every item in the list. The variable X is the item. The "$" tells the shell that what follows is a variable. However, to assign a value to a variable you omit the "$". It makes no sense, but this is the way it is.

The variable LIST_O_STUFF can also be a command that outputs a list of stuff. Well, that's exactly what pkg_info | cut -f 1 -d " " does. To tell the shell that instead of a variable we want to use a command, we enclose the command with a "`" (grave accent). It's to the left of the 1 on the keyboard.

The for-each-loop will look like this (I just like to use pkgname as the variable name):

for pkgname in `pkg_info | cut -f 1 -d " "` ; do
       pkg_create -b $pkgname
done

The shell is very picky, notice the space before and after the " ; ". That's important and the command will fail without them.

Put it all Together

Lets drop to a shell by typing in "sh". Your command prompt will change to a simple "#" sign to signify that you are in the standard shell as the root user.

sh
#

Since pkg_create will place the packages in the current directory, we may want to switch to a reasonable location to store them. For this example, I'll just place them in a sub folder named "packages" in my root home.

mkdir /root/packages
cd /root/packages

Then type the command exactly as shown and press enter.

for pkgname in `pkg_info | cut -f 1 -d " "` ; do
       pkg_create -b $pkgname
done

Normally this should produce no output errors. But if you do see any, it probably means some files were missing or different from the original port. Most of the time that can be fixed be reinstalling the port and running pkg_create for just that port.

Moments later (depending on how many ports you have), you'll have a shinny new collection of packages that make up your system's installed ports.