In this guide we'll look at file metadata in OSX and how to use it for file renaming. We'll go over reading and extracting metadata from a file. Then, we'll look at how it can be formatted and used to rename files.
This section is a recap of the code samples used throughout the guide, that you can come back to for quick reference. For the Step by Step guide skip ahead.
Note: Additionally, you can find all code snippets on our Github Gist page.
Below is the completed script that we will be building. Although it is a working script it should act as a starting point that you can customize to better fit your needs. To use it, just paste the following code in a new text file and save it as metaDateRenamer.sh.
Note: You can rename it to whatever you please but make sure to keep the
.sh extension. For more help check out the Intro to Shell Scripts section.
To launch, open a new Terminal window and paste:
Note: To access the manual page for any command in Terminal, type
Directly within Terminal
The snippets below can be run directly within Terminal (or include them in your custom script) for quick renaming.
Note: Make sure to specify your own
filename.ext and all
.ext instances. To overwrite the file, swap
Following are some building blocks to help you create your custom snippets and scripts.
2020-12-31 16:01:33 +0000
Note: These snippets are based on the Datetime format example provided above.
Formatting Date Metadata
Extracting Path, Filename, and Extension:
Step by Step Example
A file, including it's metadata, is represented by an object named
MDItem in OSX. This is what Spotlight uses to help you search for your files.
Reading a file's metadata
To list a file's metadata we can use the dedicated command
mdls - where
ls stands for "list".
To use it just open a new Terminal window, and type
mdls, tap the
Spacebar, then drag and drop any file from the Finder to the Terminal window, and hit
You should see a list of metadata attributes that belong to the file you specified. Cool!
Note: We will only cover
mdls in this guide but there are other three available commands related to metadata:
mdfind to search for files by metadata values,
mdutil for managing metadata storage, and
mdimport for testing and re-indexing files. You can see the documentation for any of those simply by typing
man in Terminal, followed by the command:
Additionally, you can specify a metadata attribute by name, before the file path:
enter and you should see the metadata item you specified and it's value.
kMDItemContentCreationDate = 2020-12-31 16:01:33 +0000
You can also specify multiple ones, like this:
Date and Time File Metadata
Files often have multiple datetime metadata attributes. Among those that you will find with almost all file types are Creation Date and Modification Date - considered common metadata attributes. Depending on the type of file (image, audio, text...) there may be more or less attributes that can be specific to the file type.
Here is a list of datetime metadata attributes you are most likely to encounter:
Common Metadata Attributes
- kMDItemContentCreationDate: The date and time that the content was created.
- kMDItemContentModificationDate: Date and time when the content of this item was modified.
- kMDItemAttributeChangeDate: Date and time of the last change made to a metadata attribute.
- kMDItemLastUsedDate: Date and time that the file was last used. This value is updated automatically by LaunchServices every time a file is opened by double clicking, or by asking LaunchServices to open a file.
Audio Metadata Attributes
- kMDItemRecordingDate: Recording date of the song or composition. This is in contrast to kMDItemContentCreationDate which, could indicate the creation date of an edited or "mastered" version of the original art.
Filesystem Metadata Attributes
- kMDItemFSContentChangeDate: Date the file contents last changed.
- kMDItemFSCreationDate: Date that the contents of the file were created.
A complete list of all metadata attributes can be found on the Apple Developer Website.
Each Datetime metadata value is presented in the same format, let's understand better what this is.
The Datetime Standard Format
It's always important to understand what data you are working with.
Dates come in this standard format:
YYYY-MM-DD hh:mm:ss +TZD.
Here is an example for reference:
2020-12-31 16:01:33 +0000.
Note: The Time Zone Designator (TZD) is the Time Offset (in hh:mm) from the UTC/GMT time: +0000. In our example the time is local to the Greenwich Mean Time (GMT).
Find out more about Datetime standards on the World Wide Web Consortium (W3C) website.
Formatting Datetime with Sed
Once we choose a Datetime attribute to work with, we can then format it. Since we want to use it in a filename, we may want to remove any whitespaces or special characters from it. To perform this kind of text manipulation we can use the
sed command in Terminal.
Sed is a stream editor. A stream editor is used to perform basic text transformations on an input stream (a file or input from a pipeline).
We can us it, for example, to substitute a word with another in a given input. Like this - where we want to replace the
s stands for substitute,
one is the word to replace, and
two is the word to replace
/ is the symbol used as a delimiter for the pattern, but you can also use
Note: Read more about the
s flag in the
Now, if you run this in your Terminal window you will get an error, since
sed is expecting a file instead of a string (as with most commands):
To pass a string into the
sed command we have a few options:
We print the filename first, then pass it as a string to the following command, using the
Note: We'll get to see why only the first instance of
one was changed and how to address that, later.
To understand piping you need to be familiar with how the UNIX shell works (on OSX, by default, we use the Bash UNIX shell). In practice, it works with streams of characters. There are three standard streams in UNIX Shells:
stdout: Output from a command.
stdin: Input to a command.
stderr: Error from a command.
We can apply this knowledge to understand what's going on in the code sample above. Where the output of the
echo command (that simply returns the string as is) is passed on as input to the
sed command so that it can perform it's operation. We can continue to pipe command outputs to new command inputs simply by appending those new commands, separated by
When working with single line strings, we can use this method to pass strings as standard input to the command. The syntax is:
Here Strings can be considered a stripped down version of Here Documents. More info on Wikipedia.
Substituting all instances
By default, the substitution occurs only for the first instance found. To apply the substitution to all instances we can use the
Note: You can also specify the exact number of instances to be substituted. Read more about it in the
sed manual entry for
s - just scroll down until you find the
In our example, we are trying to get a date string formatted as:
2012311601, from a string that originally looks like this:
We can start by removing all non-digit characters by searching for them first. Then removing them.
sed we can use a regular expression to define the search pattern.
A regular expression (shortened as regex or regexp; also referred to as rational expression) is a sequence of characters that define a search pattern. Usually such patterns are used by string searching algorithms for "find" or "find and replace" operations on strings, or for input validation. — Wikipedia.
Regular expressions can get fairly complex but in our case it is rather straightforward:
[^0-9]. This is known as a Set Expression, and the
^ specifies a negation. Practically, it will go through the string and match any character that is not within the range of
9 - is not a digit. Once the match is found it will replace that character with empty.
Now that we have only the digits, we can choose to extract a specific set. To achieve this we can use a different command:
Note: You can find all about regular expressions here: https://www.regular-expressions.info.
Slicing the formatted string with the Cut command
cut command is a nimble utility that can be used to extract text from an input. We'll use it to strip any unwanted digits from our
202012311601330000, in our example to obtain:
cut command uses characters:
-c or delimiters:
-d to perform it's slicing actions.
Slicing by Characters
When using the
-c flag, we need to specify a range.
Let's look at some examples:
So for our purpose we can use:
Slicing by Delimiters
When using the
-d flag, we need to specify a delimiter and a field. Whenever a character that matches the specified delimiter is encountered, the string will be split at that point. The resulting segments are known as fields. It will then look through the string for any other instances and continue splitting the string into separate fields. Let's look at an example:
In the above example, the specified delimiter is a space
" ". Therefore the command searches for all spaces in the string, and encounters
2. It then splits the string where each space is located, returning three fields:
Finally it returns the specified field,
-f 1 in our example above. You can specify multiple fields using comma, or a range using dash.
cut with the
-c flag to format the date in our script, but we'll use
cut with the
-d flag somewhere else: to isolate the file extension.
Extracting Path, Filename, and Extension
When dropping a file into the Terminal window, we are shown the full path of that file (including filename and extension) e.g:
/Users/yourUsername/Desktop/image.jpg. In this section we'll go over how to extract each component of a file path.
In Terminal, there are two utilities that handle this for us:
dirname to extract the path.
basename to extract the filename, including the extension.
There is no utility for separating the extension from the filename. But we can use a simple yet effective method to accomplish this.
You should now be familiar with the
<<< operator - to pass the filename as string to the
rev command. We are also using the
cut command with the delimiter option
rev command simply reverses whatever input is passed to it:
Keeping in mind that pipes
| are used to pass the output of one command as input to the next, let's go over each step.
If we pass the filepath as a string to the
rev command, it gets reversed:
Let's pipe this to our
cut command. We want to cut everything after the first
. (period) symbol - that denotes the end of the extension.
We can now use
rev again to reverse the string to it's correct form. Done!
Copy and Move Commands
Those two commands are similar to when you are copying or moving files in the Finder. They have a similar structure and options. Use them by specifying the command first, then the current filename followed by the new name.
Note: Make sure to check their
man pages as there are a few extra options you can specify.
Building the Script
Now we are ready to put the pieces together and build our custom script. It's important to know that those commands can be run one by one, directly in Terminal, but that's something to avoid when running many commands. That's where a Shell script comes in handy.
Intro to Shell Scripts
A Shell script is a program that can be run in the UNIX shell. Essentially a collection of commands that when run, are processed sequentially - one after the other.
A Shell script is identified by the
.sh extension. To run it in Terminal, we can simply type in
Here is the complete script:
There are a few outstanding elements to discuss:
Note: The first line:
#!/bin/bash, defines the path to the Shell in which to run the script in. In our case, since we are launching the script directly from Terminal using the
sh command, it can be omitted - but it's best practice to have there.
We can store a piece of data and refer to it using a variable.
To define a variable use this format:
To call it, use
There are also predefined variables that store data about your current system. These are known as system variables.
Here are some examples:
Note: It is a convention to use UPPERCASE for Unix shell variables but you can choose not to. Accepted characters are:
_. Read more about Shell variables here: https://www.tutorialspoint.com/unix/unix-using-variables.htm.
User Defined Arguments
$2 are arguments and they represent user inputs, passed to the script on launch. In our example we are requiring
2 arguments: the file to rename, and the name of the date metadata, used to rename that file. We can simply chain any additional arguments that can then be referred to in the script using
Note: When calling user defined arguments in the script, we wrap them in
"" quotes to interpret them as strings, making sure any special characters are properly escaped. There are some exceptions as well as a distinction between single and double quotes. Read more about using quotes in bash here: https://bash.cyberciti.biz/guide/Quoting.
Prompting for User Input
read command prompts the user for input and, once received, will store it in the specified variable (in our example:
NAME). The preceding
echo describes the requested input - in our case the new filename to which we will append the extracted date value. If you are unfamiliar with
echo, it is a command that outputs to the console whatever string is passed to it.
Checking if a File Exists
Finally, we can wrap the
move command in a conditional statement that checks if the specified file exists. If it does, then run the command, if not, inform the user about the it.
The conditional statement syntax is:
and to check if the file exists:
Note: You can find a list of conditionals here: https://devhints.io/bash#conditionals.
Hopefully this guide has provided you with valuable insight into how to handle metadata in OSX and the foundations for building your own Shell scripts.