So far, this blog has largely been tutorials on how to use particular commands or workflows. I'll keep doing that, but I also want to highlight some real-world problems that can easily be solved with some knowledge of shell scripting and the command line.
To start off, here's how I used the find command to create a script for Nantucket E-Books to help them easily update the code for all of their books at once.
The Setup
While I didn't have the exact file structure to work from, based on Nicholas's description I was able to infer that it looked something like this:
.
├── author1
│ ├── book1
│ │ └── assets
│ │ └── js
│ └── book2
│ └── assets
│ └── js
├── author2
│ └── book1
│ └── assets
│ └── js
├── author3
│ ├── book1
│ │ └── assets
│ │ └── js
│ ├── book2
│ │ └── assets
│ │ └── js
│ └── book3
│ └── assets
│ └── js
├── author4
│ ├── book1
│ │ └── assets
│ │ └── js
│ └── book2
│ └── assets
│ └── js
└── author5
├── book1
│ └── assets
│ └── js
├── book2
│ └── assets
│ └── js
├── book3
│ └── assets
│ └── js
└── book4
└── assets
└── js
Within each book's folder, the same three JavaScript files needed to be updated to the newest version.
the solution
My solution was a simple shell script:
#!/bin/sh
subdirs="$(find "$PWD" -mindepth 2 -maxdepth 2 -type d)" # Get the full paths of all second-level subdirectories
echo "$subdirs" | while read subdir; do # for each selected subdirectory, do the following
cd "$subdir" # moves into the correct directory
# do your actions here
done
This solution let him add the actual commands to copy the new files at the indicated location, but would make sure whatever he put there would be executed in every book's folder. For example:
cp ~/code/updated-file.js ./assets/js/file.js
The above would take the updated file and copy it to the location it belongs in each book's subdirectory.
breaking it down
Let's break this solution down a little.
To start off, I wanted to get every book's path and store it to a variable, subdirs. The find command is ideal for that purpose.
- the positional
"$PWD"argument specifies the containing directory to search within ($PWDis a variable that returns the current working directory). You could set this to the path of your parent directory if you like, to be able to run the script from anywhere, but I didn't know that path and figured running the command from the directory would suffice. -type dlimits results to directories only; by default,findlooks for both files and directories (-type fwould find files only)-mindepth 2makes sure results are at least two directories away from the base directory-maxdepth 2makes sure results are a maximum of two directories away from the base directory
Combined, -mindepth 2 and -maxdepth 2 essentially just say "find all results that are exactly two subdirectories away". In our case, this perfectly matches all book directories, since they're subdirectories of the author directories, but keeps the script from matching the assets and js directories, since those are more than two subdirectories away.
Let's test it:
/tmp/tmp.UkyqnYccbh] subdirs="$(find "$PWD" -mindepth 2 -maxdepth 2 -type d)"
/tmp/tmp.UkyqnYccbh] echo "$subdirs"
/tmp/tmp.UkyqnYccbh/author2/book1
/tmp/tmp.UkyqnYccbh/author4/book2
/tmp/tmp.UkyqnYccbh/author4/book1
/tmp/tmp.UkyqnYccbh/author1/book2
/tmp/tmp.UkyqnYccbh/author1/book1
/tmp/tmp.UkyqnYccbh/author3/book2
/tmp/tmp.UkyqnYccbh/author3/book1
/tmp/tmp.UkyqnYccbh/author3/book3
/tmp/tmp.UkyqnYccbh/author5/book4
/tmp/tmp.UkyqnYccbh/author5/book2
/tmp/tmp.UkyqnYccbh/author5/book1
/tmp/tmp.UkyqnYccbh/author5/book3
That is, indeed, all of the exact directories we want!
Next, we want to loop through all of these folders. In my last article, I mentioned the strategy of piping a list to a while loop, which I've done again here.
echo "$subdirs" | while read subdir; do
# …
done
An important note is that pipes like this create subshells, meaning that any variables I set within the loop cannot affect the rest of the script. For this use case, that doesn't matter (since created files/directories will of course remain after the subshell finishes), but it's good to be aware of when adapting this script to your own purposes.
Now that we are in the loop, we'll move into the subdirectory that's currently being processed:
cd "$subdir"
Easy peasy. As we mentioned, anything else we want done while in this folder can now be added.
the results
Nicholas's immediate response?
I just updated the software and stylesheets for forty-eight e-books on the site with one command.
He's written a blog post of his own that mentions this solution, which is also worth reading. And while you're there, check out the platform, it's a great and easily accessible platform to read books for free from any device, with built-in audiobooks. It's a great project, and I'm proud to have been able to lighten the load of keeping it running.
conclusion
I'll leave off there. If you have solutions to real-world problems of your own to continue this series or a real-world problem you need help solving with the shell, I'd love if you'd send me a message! It can be like this article, where I wrote about a solution to someone else's problem, or you can write a guest article for publication on this blog.
My email (and PGP key!) are below, I'd love to talk through your ideas with you. See you next time!
Comment via Fediverse