Find and Act Quickly on Files
Today, I had a directory of files, and I wanted to copy a subset of those files into another directory, following the exact same directory structure. More specifically, I had a directory structure that looked like this:
> ls dir
2009/12/directory/one.mp3
2009/12/directory/one.mp3.1
2009/12/directory/two.mp3
2009/12/another_directory/three.mp3.1
2010/01/another_directory/fourty.mp3
...
In my case, I wanted to copy all the .1 files alone into another directory, such that I end up with:
> ls working_dir
2009/12/directory/one.mp3.1
2009/12/another_directory/three.mp3.1
Copying them by hand wasn’t an option, given that there were over 300 of these files spread out across these subdirectories.
I’ve really love the fd tool as a replacement for find due to its speed and performance. Initially, I was trying to use a combination of fd and shell scripting to do what I want. This worked initially, but I found edge cases where it didn’t work the way I expected it to. At that point, I remembered that fd supports running commands on the file results. I didn’t realize just how much time this would save. I ended up accomplishing the above by using the following 2 commands:
fd -e 1 -x mkdir -p working_dir/{//}
fd -e 1 -x cp {} working_dir/{}
fd -e 1 -x rm
Let’s break this down:
fd -e 1searches for files with the extension1.-xis for executing a command on each result (in parallel).mkdir -pis what I want to do - if I haveoriginal/2009/12/directory/one.mp3.1, I need to make the directory holding this file inworking_dirto be able to copy the file to it.mkdir -pallows me to do that, while making intermediate directories as required.{//}is a placeholder infd, which tells it to replace the result with the path of the result.
The second command uses {}, a placeholder which will be replaced by the full path of the search result. The third command erases those files. Note the missing {}, since if there is no placeholder, fd will automatically add {} at the end.
Consequently, these commands translate each search result into a mkdir and then a cp to accomplish what we want, followed by a deletion to remove the file - i.e.:
> 2020/06/directory/file.mp3.1
mkdir -p destination_directory/2020/06/directory
cp 2020/06/directory/file.mp3.1 destination_directory/2020/06/directory/file.mp3.1
rm 2020/06/directory/file.mp3.1
Awesome! There are also other placeholders, such as {.} (file path without the extension), {/} (file name without the path), and {/.} (file without the path and without the extension).