EDIT: Better approach with a function rather than recursive calls to the same script.
Here's hoping I got all corner cases. This descends into directories recursively to handle deeply nested directory trees.
Caveat: Because the script takes care to not overwrite existing files, gaps may appear in the numbering in some corner cases -- if there is a file 0.txt
in a directory and the first file that is handled in that directory is a .txt
file, it will be moved to 1.txt
. This also happens if the first file that is handled is 0.txt
, so running the script twice will change the numbering, and running it again will change it back.
So here's the code:
#!/bin/bash
handle_directory() {
local counter=0
for i in *; do
# if the file is a directory (but not a symlink),
# handle it before moving
if [ -d "$i" ] && ! [ -h "$i" ]; then
cd "$i"
handle_directory
cd ..
fi
# extract suffix
suffix="${i##*.}"
if [ "$suffix" != "$i" ]; then
extension=".$suffix"
else
# If there is no filename extension, the counter
# is the whole filename. Without this, we'd get
# 0.Makefile and suchlike.
extension=""
fi
# find a filename that isn't already taken
# this may lead to gaps in the numbering.
while dest="$counter$extension" && [ -e "$dest" ]; do
let ++counter
done
echo mv "$i" "$dest"
let ++counter
done
}
# if a parameter was given, go there to handle it.
# otherwise handle the local directory.
if ! [ -z "$1" ] && ! cd "$1"; then
echo "Could not chdir to directory $1"
exit -1
fi
handle_directory
The general idea is a depth-first search of the directory tree in question. Like any tree, the directory tree is best handled recursively, and the function essentially boils down to: Walk through all things in this directory, if they are a directory, descend and handle it, then find an appropriate file name and rename the thing whether it is a directory or not.
Things used:
local counter=0 # declares a function-local variable counter and initializes it
# to 0. crucially, that it is local means that every invocation
# of handle_directory has its own counter.
[ -d "$i" ] # tests if "$i" is a directory
[ -h "$i" ] # tests if "$i" is a symlink
! [ ... ] # negates the test. ! [ -h "$i" ] is true if "$i" is NOT a symlink
"${i##*.}" # a bashism that cuts off the longest prefix that matches the pattern
[ -e "$dest" ] # tests if "$dest" exists
$1 # the first parameter with which the script is called
[ -z "$1" ] # tests if "$1" is an empty string
! cd "$1" # descends into the directory "$1". true if that failed.
Manpages to read to further understanding:
man test
man bash # that's the big one.