Shell scripts to delete old Jenkins builds – Yaroslav Grebnov


A Jenkins server used by multiple computers frequently requires periodic cleaning. Old builds that are not used should be deleted. Such a task can be easily automated and configured to run periodically using crontab. This blog post explains several examples of shell scripts that serve this purpose.

Deleting builds older than X days

This is a basic example of a Jenkins job build deletion script. We will remove all compilations of modified jobs more than 3 days ago. If the job hierarchy is flat (there are no folders), the script is just a one-line search command, elegant but complex


of command elements:


  • jobs/*/builds – we are looking for all builds of all jobs located in the Jenking home directory, for example /var/lib/jenkins. The asterisk * replaces all
  • working folder names,-maxdepth 1 means we are looking for

  • direct children of the build directory
  • ,-type d means we are looking for

  • directories
  • ,-

  • mtime +3 selects items modified more than 3 days ago,
  • -name “

  • [0-9]*” means we are looking for items with names that start with a number. This part is added so as not to delete the actual build directory,
  • exec rm -rf {} \; force deletes all found items.

Deleting builds older than X days larger than Y kilobytes

Since the build directory

usually contains more than one file, and to calculate the total size of the directory we need to add the sizes of all the files and directories it contains, in this example we will not be able to use only the find command. We will use a for-loop.

We will reuse some of the requirements from the basic example. We will remove all compilations of modified jobs more than 3 days ago.

To make the example more interesting, we will assume that the job hierarchy has two levels, which means that at the root level we can have jobs and folders that contain jobs. In addition, we will consider works that contain spaces in their names.

In addition, we will record each deletion, specifying the name and size of each deleted folder.

Script explanation elements:

log file log_file=delete_jenkins_jobs-$(date +%Y-%m-%d) creates a variable that contains the name of the

log file

. The name is based on the IFS-formatted timestamp

Three lines relating to


address the requirement for possible space characters in work names. In for-loop we are using an array created from the output of the find command. By default, the IFS variable contains the space character, so directories found that contain spaces in their names may be incorrectly split into two entries. For example, the /var/lib/jenkins/jobs/some job/builds/1 directory with the default IFS will be represented as two elements in the for-loop array: /var/lib/jenkins/jobs/some and job/builds/1. To represent a job with space characters in its name as an element in the for-loop array, we modify the IFS, so that only the new line character is considered as a separator. This change is revered after

the for-loop loop

We are using a for-loop of the following format:

builds array used in for-loop

The array is created from the concatenated output of the two find commands.

We have two commands because we look for builds in paths using two patterns:


  • */builds – for jobs defined at the root level. The path to a root-level job named
  • someJob will be /var/lib/jenkins/jobs/someJob,/var/lib/jenkins/jobs/*/*/

  • */builds, for jobs located in folders. The path to the someJob job located in the someFolder folder will be /var/lib/jenkins/jobs/someFolder/jobs/someJob. As you can see, the jobs part of the route is repeated twice, which is how it is implemented in Jenkins. This repetition is why we have three asterisks in the route pattern.

An imported note about the output concatenation of search commands. We need to have a spatial character between them. In case we do not put that space, the first element of the second output will be added to the last element of the first output and this pair will be considered as an element of the array, like this: “firstLastsecondFirst” instead of: “fisrtLast”, “secondFirst”.

find command

Compared to the basic exMacle, we have one more modification. There are 2>/dev/null at the end. This part is used to ignore errors when examining the directory structure. An example of such an error can be the lack of permission for the current user to access some directory. Without part 2>/dev/null, error messages will be added to the for-loop array, which is incorrect, because we only want paths to the directories there.

Compare directory size to

threshold value


to the requirements, we delete directories larger than 500000 kilobytes. To compare a given directory size with the threshold value, we use an if:where

-gt condition means greater than.

Get the directory size To get a

directory size f for further comparison, we use two pipelined commands

: du -sk – $f – a du


  • with two flags: 1) s which means summarize, i.e. take the total size of all the elements inside and 2) k means that 1 in the output is equal to 1 kilobyte,
  • awk ‘

  • {print $1}’ – An awk command that prints the first word of the output of the DU command. The first word is the actual size in kilobytes.



directory echo “

Deleted: $(du -sk – $f)” >> /var/log/$log_file adds a log with the name and size of the deleted directory at the end of the log file. >> means to append. $log_file was defined at the beginning of the script



rm -rf directory $f deletes a named directory from the f variable.

Delete builds

older than X days, but skip those marked to keep forever

Jenkins has the functionality of not keeping more than N builds for some work. In addition, we can mark a build to be ‘kept forever’ by checking the corresponding checkbox in the user interface. This way, Jenkins will skip that compilation during the removal process.

In the automatic job build removal script, we would like to take this functionality into account. Therefore, the third example demonstrates how to exclude flagged builds to stay forever from the deletion process.

To keep things simple, we’ll remove the requirements for the non-flat job hierarchy and build directory size.

In the explanation we will address only the new elements script compared to the previous example


skip builds marked to be saved forever

This requirement is implemented by checking if the result of piping two commands: grep and wc: $(grep “<keepLog>true</keepLog>” $f/build.xml 2>/dev/null | wc -l) is equal to zero.

Let’s examine the grep part “<keepLog>true</keepLog>” $f/build.xml 2>/dev/null. A working build configuration in Jenkins is stored internally in a file build.xml located in that build directory. Builds that are marked as Must Keep Forever have a keepLog element equal to true. In other words, such a build.xml file contains text <keepLog>true</keepLog>. So, we’re using the grep command to find that text in the build.xml file. By default, we consider builds not marked to be preserved forever. That’s why we ignore all grep command execution errors using this part: 2>/dev/null.

The execution output of the grep command serves as input for the wc -l command that counts the number of lines in its input.

Then, the command grep “<keepLog>true</keepLog>” $f/build.xml 2>/

dev/null | wc -l generates the number of occurrences of the text <keepLog>true</keepLog> in the build.xml file. Next, we check if this number is equal to zero. In other words, we consider a build as the one marked to stay forever if there is a text <keepLog>true</keepLog> in the build file.xml located in that build directory.

In case you have any questions or comments about this post, please send them to: