Skip to content

F2 tutorial

Ayooluwa Isaiah edited this page Feb 16, 2024 · 3 revisions

This document presents several ways in which F2 may be utilised to perform a bulk renaming operation.

Important note for Windows users: Always use double quotes when passing arguments to any flag in Command Prompt (CMD). PowerShell users may use single or double quotes.

General usage

f2 FLAGS [OPTIONS] [PATHS TO FILES OR DIRECTORIES...]
f2 FIND [REPLACE] [PATHS TO FILES OR DIRECTORIES...]

Things to note

  • When using F2, you must provide one of the -f, -u, -r, or --csv flags. The other flags are optional (see f2 -h). You may also provide the find and replace patterns are positional arguments but this is less flexible than the full syntax.

  • F2 defaults to searching the current directory by default if no paths are specified after the options.

  • You may provide one or more paths to files and directories after all the options. They can be relative or absolute paths or a combination of the two.

    f2 -f 'a' -r 'b' paths/to/dir paths/to/file.txt

    If the argument is a directory, F2 will search inside the directory for matches. If its a file, F2 will run the find string against the file to see if it matches.

  • The entire filename (including its extension) is included in the renaming operation by default.

  • Hidden files are excluded by default.

  • F2 runs in dry run mode by default. To execute the renaming operation, you must include the -x or --exec option.

Basic find and replace

Replace one or more consecutive spaces with a single underscore:

f2 -f '[ ]{1,}' -r '_'
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                                          | RENAMED                                           | STATUS |
| ************************************************************************************************************** |
| Screenshot from 2022-04-12 14:37:35.png           | Screenshot_from_2022-04-12_14:37:35.png           | ok     |
| Screenshot from 2022-04-12 15:58:55.png           | Screenshot_from_2022-04-12_15:58:55.png           | ok     |
| Screenshot from 2022-06-03 11:29:16.png           | Screenshot_from_2022-06-03_11:29:16.png           | ok     |
| Screenshot from 2022-09-26 21:19:15.png           | Screenshot_from_2022-09-26_21:19:15.png           | ok     |
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Arguments to -f are treated as regular expressions by default. When you need to rename files containing regex special characters (such as . + ? ^ { } and others), you may escape the characters by prefixing them with a backslash:

f2 -f '\(2021\)' -r '[2022]'
┌──────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                             | RENAMED                              | STATUS |
| ************************************************************************************ |
| No Pressure (2021) S01.E01.2160p.mp4 | No Pressure [2022] S01.E01.2160p.mp4 | ok     |
| No Pressure (2021) S01.E02.2160p.mp4 | No Pressure [2022] S01.E02.2160p.mp4 | ok     |
| No Pressure (2021) S01.E03.2160p.mp4 | No Pressure [2022] S01.E03.2160p.mp4 | ok     |
└──────────────────────────────────────────────────────────────────────────────────────┘

Another option is to apply the -s option to treat the search pattern as a non-regex string, hence no need to escape the characters anymore:

f2 -f '(2021)' -r '[2022]' -s
┌──────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                             | RENAMED                              | STATUS |
| ************************************************************************************ |
| No Pressure (2021) S01.E01.2160p.mp4 | No Pressure [2022] S01.E01.2160p.mp4 | ok     |
| No Pressure (2021) S01.E02.2160p.mp4 | No Pressure [2022] S01.E02.2160p.mp4 | ok     |
| No Pressure (2021) S01.E03.2160p.mp4 | No Pressure [2022] S01.E03.2160p.mp4 | ok     |
└──────────────────────────────────────────────────────────────────────────────────────┘

Using positional arguments

F2 supports positional arguments for the find and replacement pattern since v1.8.0 to make .

For example, instead of using the command below to replace 'abc' with '123':

f2 -f 'abc' -r '123' -x

You can use:

f2 'abc' '123'

This syntax is less flexible than the full one but its great for performing quick renaming operations. It also dry-runs by default and uses a prompt to confirm the changes before applying them to the filesystem.

Limit the number of replacements

F2 replaces all matches in a filename by default. In situations where this behavior is not desired, you may use the -l option as follows:

f2 -f 'abc' -r '123' -l 1
┌────────────────────────────────────────────┐
| ORIGINAL        | RENAMED         | STATUS |
| ****************************************** |
| abc.txt         | 123.txt         | ok     |
| abc_abc.txt     | 123_abc.txt     | ok     |
| abc_abc_abc.txt | 123_abc_abc.txt | ok     |
└────────────────────────────────────────────┘

This means that only one match will be replaced in each filename. If you want to replace two matches, set -l to 2, and so on.

Notice that the matches at the beginning of the file are replaced first. This can be reversed by setting -l to a negative integer. For example, here's how to replace the last two matches:

f2 -f 'abc' -r '123' -l -2
┌────────────────────────────────────────────┐
| ORIGINAL        | RENAMED         | STATUS |
| ****************************************** |
| abc.txt         | 123.txt         | ok     |
| abc_abc.txt     | 123_123.txt     | ok     |
| abc_abc_abc.txt | abc_123_123.txt | ok     |
└────────────────────────────────────────────┘

If you set -l to zero, all matches in each filename will be replaced (the default).

Renaming files recursively

When you want to apply a renaming operation to subdirectories, you can use the -R option. This option does not impose a depth limit when searching for matches.

When recursively replacing files (through -R or --recursive), there is no depth limit to the amount of directories that will be traversed by default. Here's an example that replaces all instances of js to ts in the current directory and all sub directories.

f2 -f 'js' -r 'ts' -R
┌────────────────────────────────────────────────────┐
| ORIGINAL            | RENAMED             | STATUS |
| ************************************************** |
| index-01.js         | index-01.ts         | ok     |
| index-02.js         | index-02.ts         | ok     |
| one/index-03.js     | one/index-03.ts     | ok     |
| one/index-04.js     | one/index-04.ts     | ok     |
| one/two/index-05.js | one/two/index-05.ts | ok     |
| one/two/index-06.js | one/two/index-06.ts | ok     |
└────────────────────────────────────────────────────┘

The --max-depth or -m flag can be used to provide a maximum depth limit:

f2 -f 'js' -r 'ts' -R -m 1
┌────────────────────────────────────────────┐
| ORIGINAL        | RENAMED         | STATUS |
| ****************************************** |
| index-01.js     | index-01.ts     | ok     |
| index-02.js     | index-02.ts     | ok     |
| one/index-03.js | one/index-03.ts | ok     |
| one/index-04.js | one/index-04.ts | ok     |
└────────────────────────────────────────────┘

Renaming directories

Directories are exempted from the renaming operation by default. Use the -d or --include-dir flag to include them.

Original tree:

.
├── pic1.jpg
├── pic2.png
└── pics
    ├── pic3.webp
    └── pic4.avif
f2 -f 'pic' -r 'image'

Notice that the pics directory is exempt here:

┌────────────────────────────────┐
| ORIGINAL | RENAMED    | STATUS |
| ****************************** |
| pic1.jpg | image1.jpg | ok     |
| pic2.png | image2.png | ok     |
└────────────────────────────────┘

It is included when you add the -d flag:

f2 -f 'pic' -r 'image' -d
┌────────────────────────────────┐
| ORIGINAL | RENAMED    | STATUS |
| ****************************** |
| pic2.png | image2.png | ok     |
| pic1.jpg | image1.jpg | ok     |
| pics     | images     | ok     |
└────────────────────────────────┘

If you want to rename the directory alone, you can use the -D or --only-dir flag instead:

f2 -f 'pic' -r 'image' -D
┌─────────────────────────────┐
| ORIGINAL | RENAMED | STATUS |
| *************************** |
| pics     | images  | ok     |
└─────────────────────────────┘

Note: When you replace a directory and its contents in the same operation, F2 organizes the operation such that the files are renamed first before the directory. This prevents the situation where the file paths are not found after renaming the directory.

f2 -f 'pic' -r 'image' -R -d

Notice how the pics directory is at the bottom of the list.

┌────────────────────────────────────────────┐
| ORIGINAL       | RENAMED          | STATUS |
| ****************************************** |
| pics/pic4.avif | pics/image4.avif | ok     |
| pics/pic3.webp | pics/image3.webp | ok     |
| pic2.png       | image2.png       | ok     |
| pic1.jpg       | image1.jpg       | ok     |
| pics           | images           | ok     |
└────────────────────────────────────────────┘

The reverse situation occurs when undoing such an operation. Renamed directories are reverted first before the files.

Ignoring file extensions

F2 matches the file extension by default, but if this behaviour is not desired, you can use the --ignore-ext or -e flag.

Without the -e flag

f2 -f 'jpeg' -r 'jpg'
┌────────────────────────────────────────────┐
| ORIGINAL         | RENAMED        | STATUS |
| ****************************************** |
| a-jpeg-file.jpeg | a-jpg-file.jpg | ok     |
| file.jpeg        | file.jpg       | ok     |
└────────────────────────────────────────────┘

With the -e flag

f2 -f 'jpeg' -r 'jpg' -e
┌─────────────────────────────────────────────┐
| ORIGINAL         | RENAMED         | STATUS |
| ******************************************* |
| a-jpeg-file.jpeg | a-jpg-file.jpeg | ok     |
└─────────────────────────────────────────────┘

Stripping out unwanted text

You can strip out text by leaving out the -r or --replace flag. Here's an example that removes -unsplash from the end of some image files:

f2 -f '-unsplash'
┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                                          | RENAMED                                  | STATUS |
| ***************************************************************************************************** |
| nathan-anderson-7TGVEgcTKlY-unsplash.jpg          | nathan-anderson-7TGVEgcTKlY.jpg          | ok     |
| pang-yuhao-WxREM3u9ytk-unsplash.jpg               | pang-yuhao-WxREM3u9ytk.jpg               | ok     |
| rich-hay-PQzlMO1ifPA-unsplash.jpg                 | rich-hay-PQzlMO1ifPA.jpg                 | ok     |
| samuele-errico-piccarini-t4OxCpKie70-unsplash.jpg | samuele-errico-piccarini-t4OxCpKie70.jpg | ok     |
| valentin-salja-VMroCCpP648-unsplash.jpg           | valentin-salja-VMroCCpP648.jpg           | ok     |
| vimal-s-GBg3jyGS-Ug-unsplash.jpg                  | vimal-s-GBg3jyGS-Ug.jpg                  | ok     |
└───────────────────────────────────────────────────────────────────────────────────────────────────────┘

Renaming using an auto incrementing number

You can specify an auto incrementing integer in the replacement string using the format below:

  • %d: 1,2,3 e.t.c
  • %02d: 01, 02, 03, e.t.c.
  • %03d: 001, 002, 003, e.t.c.

...and so on.

f2 -f '.*\.' -r '{%03d}.'
┌───────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                                                                       | RENAMED | STATUS |
| ************************************************************************************************* |
| Screenshot 2022-06-18 at 11-31-43 https __vale.dev.png                         | 001.png | ok     |
| Screenshot 2022-06-18 at 11-39-27 Ante.png                                     | 002.png | ok     |
| Screenshot 2022-06-18 at 13-49-10 redbean zip listing.png                      | 003.png | ok     |
| Screenshot 2022-06-18 at 13-50-03 redbean zip listing.png                      | 004.png | ok     |
| Screenshot 2022-06-24 at 20-50-50 Should You Get a Ceiling or Standing Fan.png | 005.png | ok     |
| Screenshot 2022-07-19 at 08-59-59 Green Africa - Itinerary.png                 | 006.png | ok     |
└───────────────────────────────────────────────────────────────────────────────────────────────────┘

You can learn more about how to customise the indexing (such as the number to start from, numbers to skip, e.t.c) here.

Ignoring cases

F2 is case sensitive by default, but the -i or --ignore-case flag can be used to make it insensitive to letter cases:

f2 -f 'jpeg' -r 'jpg' -i
┌─────────────────────────────┐
| ORIGINAL | RENAMED | STATUS |
| *************************** |
| a.JPEG   | a.jpg   | ok     |
| b.jpeg   | b.jpg   | ok     |
| c.jPEg   | c.jpg   | ok     |
└─────────────────────────────┘

Using regex capture variables

Regex capture variables are also supported in the replacement string. It can come in handy when you want to base the new filenames on elements in the existing names:

f2 -f '.*-(.*)_(.*)' -r '$1. $2' -e
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                                               | RENAMED                            | STATUS |
| **************************************************************************************************** |
| The Weeknd_Dawn FM_1-01_Dawn FM.flac                   | 01. Dawn FM.flac                   | ok     |
| The Weeknd_Dawn FM_1-02_Gasoline.flac                  | 02. Gasoline.flac                  | ok     |
| The Weeknd_Dawn FM_1-03_How Do I Make You Love Me.flac | 03. How Do I Make You Love Me.flac | ok     |
| The Weeknd_Dawn FM_1-04_Take My Breath.flac            | 04. Take My Breath.flac            | ok     |
| The Weeknd_Dawn FM_1-05_Sacrifice.flac                 | 05. Sacrifice.flac                 | ok     |
| The Weeknd_Dawn FM_1-06_A Tale by Quincy.flac          | 06. A Tale by Quincy.flac          | ok     |
| The Weeknd_Dawn FM_1-07_Out of Time.flac               | 07. Out of Time.flac               | ok     |
| The Weeknd_Dawn FM_1-08_Here We Go… Again.flac         | 08. Here We Go… Again.flac         | ok     |
| The Weeknd_Dawn FM_1-09_Best Friends.flac              | 09. Best Friends.flac              | ok     |
| The Weeknd_Dawn FM_1-10_Is There Someone Else.flac     | 10. Is There Someone Else.flac     | ok     |
| The Weeknd_Dawn FM_1-11_Starry Eyes.flac               | 11. Starry Eyes.flac               | ok     |
| The Weeknd_Dawn FM_1-12_Every Angel Is Terrifying.flac | 12. Every Angel Is Terrifying.flac | ok     |
| The Weeknd_Dawn FM_1-13_Don’t Break My Heart.flac      | 13. Don’t Break My Heart.flac      | ok     |
| The Weeknd_Dawn FM_1-14_I Heard You’re Married.flac    | 14. I Heard You’re Married.flac    | ok     |
| The Weeknd_Dawn FM_1-15_Less Than Zero.flac            | 15. Less Than Zero.flac            | ok     |
| The Weeknd_Dawn FM_1-16_Phantom Regret by Jim.flac     | 16. Phantom Regret by Jim.flac     | ok     |
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘

Organizing files into new or existing directories

If a path separator (/ in all OSes and \ in Windows only) is present in the replacement argument, it will be treated as a path with the last portion being the file or directory name.

Assuming you want to organize some files by categorizing them into subfolders, you only need to add a forward slash in the replacement string such as in the example below:

f2 -f '[^a-zA-Z]*([^(]*) \(([^)]*)\).*\.iso$' -r 'Games/$2/$1{ext}'
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                                                         | RENAMED                                           | STATUS |
| ***************************************************************************************************************************** |
| 0956 - Call of Duty - Roads to Victory (Germany) (v1.01) [b].iso | Games/Germany/Call of Duty - Roads to Victory.iso | ok     |
| 1925 - Gran Turismo (USA) (En,Fr,Es).iso                         | Games/USA/Gran Turismo.iso                        | ok     |
| 2233 - Metal Gear Solid - Peace Walker (USA) (v1.01).iso         | Games/USA/Metal Gear Solid - Peace Walker.iso     | ok     |
| 3185 - Pro Evolution Soccer 2014 (Europe) (It,El).iso            | Games/Europe/Pro Evolution Soccer 2014.iso        | ok     |
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

When executing the operation above, F2 will auto create the path directories where necessary.

Filtering out matches

F2 provides the --exclude or -E flag for the excluding files that would have been matched through the find pattern. This allows you to drill down your search to be specific as possible.

Assuming you want to rename some Webp files, you can do something like this:

f2 -f '.*\.webp' -r '%03d{ext}'
┌───────────────────────────────────────────────────────────────────┐
| ORIGINAL                                      | RENAMED  | STATUS |
| ***************************************************************** |
| 34e7dcd7.webp                                 | 001.webp | ok     |
| 3a2107b350fe4173f09a38deebd715bc.webp         | 002.webp | ok     |
| 7ukau2yqm8y91.webp                            | 003.webp | ok     |
| 7zwffegy91871.webp                            | 004.webp | ok     |
| 9ru90wxqm8y91.webp                            | 005.webp | ok     |
| alan_p.webp                                   | 006.webp | ok     |
| bgtqgsxqm8y91.webp                            | 007.webp | ok     |
| dashboard5.webp                               | 008.webp | ok     |
| eiuz4tzc2le71.webp                            | 009.webp | ok     |
| image-2-1.webp                                | 010.webp | ok     |
| qeila4tnvr971.webp                            | 011.webp | ok     |
| sa3a4zxqm8y91.webp                            | 012.webp | ok     |
└───────────────────────────────────────────────────────────────────┘

Let's say you want to exclude files that contain the number 9, you can do something like this:

f2 -f '.*\.webp' -r '%03d{ext}' -E '9'
┌───────────────────────────────────────────────────────────────────┐
| ORIGINAL                                      | RENAMED  | STATUS |
| ***************************************************************** |
| 34e7dcd7.webp                                 | 001.webp | ok     |
| alan_p.webp                                   | 002.webp | ok     |
| dashboard5.webp                               | 003.webp | ok     |
| eiuz4tzc2le71.webp                            | 004.webp | ok     |
| image-2-1.webp                                | 005.webp | ok     |
| splunk-log-observer-hero-dashboard-plain.webp | 006.webp | ok     |
└───────────────────────────────────────────────────────────────────┘

Targeting specific files

Another way to narrow down the find and replace operation to specific files is to list the paths to the desired files directly after all the command line options. Assuming the following directory:

ls
sample_flac.flac sample_mp3.mp3 sample_ogg.ogg

Suppose you want to rename sample to example but only on the sample_flac.flac and sample_mp3.mp3 files, you can specify the files that should be matched directly:

f2 -f "sample" -r "example" sample_flac.flac sample_mp3.mp3
┌───────────────────────────────────────────────┐
| ORIGINAL         | RENAMED           | STATUS |
| ********************************************* |
| sample_flac.flac | example_flac.flac | ok     |
| sample_mp3.mp3   | example_mp3.mp3   | ok     |
└───────────────────────────────────────────────┘

Notice that the sample_ogg.ogg file isn't included even though it matches the find pattern. When you specify a set of files as above, only those files will be searched for matches even if other files in the current directory match the specified pattern.

Chaining renaming operations

With F2, you can chain as many renaming operations as you want in a single command. This makes it easy to selectively replace different aspects of file names. The first combination selects the pool of files that will be worked on, while subsequent ones act on the results of the previous one. If this concept sounds confusing, this example below should make it clearer:

f2 -f '[^a-zA-Z]*([^(]*) \\(([^)]*)\\).*\\.iso$' -r 'Games/$2/$1{ext}' -f ' - ([^.]*)' -r ' ({<$1>.up})'
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                                                         | RENAMED                                           | STATUS |
| ***************************************************************************************************************************** |
| 0956 - Call of Duty - Roads to Victory (Germany) (v1.01) [b].iso | Games/Germany/Call of Duty (ROADS TO VICTORY).iso | ok     |
| 1925 - Gran Turismo (USA) (En,Fr,Es).iso                         | Games/USA/Gran Turismo.iso                        | ok     |
| 2233 - Metal Gear Solid - Peace Walker (USA) (v1.01).iso         | Games/USA/Metal Gear Solid (PEACE WALKER).iso     | ok     |
| 3185 - Pro Evolution Soccer 2014 (Europe) (It,El).iso            | Games/Europe/Pro Evolution Soccer 2014.iso        | ok     |
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Notice how the -f and -r flags can be used multiple times in a single renaming operation. The first combination (-f '[^a-zA-Z]*([^(]*) \\(([^)]*)\\).*\\.iso$' -r 'Games/$2/$1{ext}') yields the following result:

Games/Germany/Call of Duty - Roads to Victory.iso
Games/USA/Gran Turismo.iso
Games/USA/Metal Gear Solid - Peace Walker.iso
Games/Europe/Pro Evolution Soccer 2014.iso

Afterward, the next combination targets the characters after hypen, uppercases them and places them in parenthesis in the new name thus yielding the final result.

Integration with other programs

You can combine F2 with other tools using pipes to pass arguments.

Usage with find and xargs

Find files modified before the last 30 days and rename them:

find -type f -mtime +30 | xargs f2 -r '{mtime.YYYY}-{mtime.MM}-{mtime.DD}_{f}{ext}'

┌─────────────────────────────────────────────────────────────────────────────────────────────────┐
| ORIGINAL                             | RENAMED                                         | STATUS |
| *********************************************************************************************** |
| upload-files-go.md                   | 2022-10-23_upload-files-go.md                   | ok     |
| windows-terminal.md                  | 2022-10-23_windows-terminal.md                  | ok     |
└─────────────────────────────────────────────────────────────────────────────────────────────────┘

Usage with fd

You can use fd (find alternative) to generate input for F2.

Find files modified since 2022-01-01 and rename them:

fd --change-newer-than '2020-04-01 00:00:00' | f2 -r '{{mtime.YYYY}}-{{mtime.MM}}-{{mtime.DD}}_{{f}}' -e --sortr 'mtime'
+------------------------------------+-----------------------------------------------+--------+
|               INPUT                |                    OUTPUT                     | STATUS |
+------------------------------------+-----------------------------------------------+--------+
| javascript-calculator.md           | 2020-10-21_javascript-calculator.md           | ok     |
| migrating-from-google-analytics.md | 2020-10-21_migrating-from-google-analytics.md | ok     |
| pomodoro.md                        | 2020-10-21_pomodoro.md                        | ok     |
| simon-game.md                      | 2020-10-21_simon-game.md                      | ok     |
| distributing-go-binaries.md        | 2021-06-21_distributing-go-binaries.md        | ok     |
| philosophy-of-software-design.md   | 2021-07-07_philosophy-of-software-design.md   | ok     |
| windows-terminal.md                | 2021-07-18_windows-terminal.md                | ok     |
| typescript-rails.md                | 2021-07-26_typescript-rails.md                | ok     |
| upload-files-go.md                 | 2021-07-30_upload-files-go.md                 | ok     |
| rust-coreutils.md                  | 2021-07-31_rust-coreutils.md                  | ok     |
| first-chrome-extension.md          | 2021-08-01_first-chrome-extension.md          | ok     |
| linting-go.md                      | 2021-08-01_linting-go.md                      | ok     |
| fish-shell-3.md                    | 2021-08-01_fish-shell-3.md                    | ok     |
| mongodb-golang.md                  | 2021-08-06_mongodb-golang.md                  | ok     |
+------------------------------------+-----------------------------------------------+--------+

F2 can do so much more than what has been covered above. See the following links to learn more: