Batch replace files

I recently had to replace hundreds of logos across multiple pages in a client project. The client had updated their partner logos and they appeared in various pages of the site. One great thing about file-based CMSs like Kirby is that we can use simple command line tools to manage our content. While I often use search & replace for text changes, this was the first time I automated file replacements.

The challenge

Let’s say you have a folder structure like this and you want to replace the old partner logos with updated versions:

Alternative text description for the folder structure The following code block shows a folder structure. The content folder contains a projects folder and an about folder. Inside projects are two subfolders: project-a containing logo-a.png, and project-b containing logo-b.png. The about folder contains another copy of logo-a.png. Separately, there’s a new-logos folder containing the updated versions of both logo-a.png and logo-b.png.
📁 content/
├── 📁 projects/
│   ├── 📁 project-a/
│   │   └── 🖼️ logo-a.png
│   └── 📁 project-b/
│       └── 🖼️ logo-b.png
└── 📁 about/
    └── 🖼️ logo-a.png

📁 new-logos/
├── 🖼️ logo-a.png
└── 🖼️ logo-b.png

The solution

Before running commands like this, remember to back up your content folder.
Shit happens, and you don’t want to lose your original files.

Here’s a simple bash command that does the job:

find content -type f -name "*.png" -exec sh -c 'cp -v new-logos/$(basename {}) {}' \;

It will return a log like this for the folder structure above:

new-logos/logo-a.png -> content/projects/project-a/logo-a.png
new-logos/logo-a.png -> content/about/partners/logo-a.png
new-logos/logo-b.png -> content/projects/project-b/logo-b.png
new-logos/logo-b.png -> content/about/partners/logo-b.png

Let’s break it down:

  • find content Start searching in the content directory
  • -type f Look for files (not folders)
  • -name "*.png" Match files ending in png
  • -exec sh -c Run a shell command for each file found
  • basename {} Extract just the filename from the full path
  • cp -v Copy command with verbose output
  • new-logos/$(basename {}) Use the extracted filename to find the new version
  • {} The original file’s full path (where to copy to)
  • \; End the -exec command

For example, when the command finds content/projects/project-a/logo-a.png:

  1. Extracted basename: logo-a.png
  2. This creates the source path new-logos/logo-a.png
  3. The file is copied to the original location content/projects/project-a/logo-a.png

If you want to do a dry run to see what files would be replaced, you can use this:

find content -type f -name "*.png" -exec sh -c 'echo "new-logos/$(basename {}) -> {}"' \;

Different file types

The command only handles .png files because of the -name "*.png" part. You can adjust it or remove it altogether to replace any file that has a matching name:

find content -type f -exec sh -c 'cp -v new-logos/$(basename {}) {}' \;

Just be careful with this version – it will try to replace every file in your content folder that has a matching filename in new-logos/, regardless of its type.