Created
February 16, 2021 12:03
-
-
Save nickjj/f0e65838f8b871307c5b57dadd56d591 to your computer and use it in GitHub Desktop.
A zero dependency Python 3 CLI script to increment file names. This script is explained on video at: https://nickjanetakis.com/blog/a-python-script-to-increment-file-names-starting-at-a-specific-number
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 1. Create the file inc-file-names anywhere on your dev box | |
touch inc-file-names | |
# 2. Make it executable | |
chmod +x inc-file-names | |
# 3. Copy the script below into the file you just created | |
# 4. Move it onto your system path | |
sudo mv inc-file-names /usr/local/bin | |
# 5. Verify it works | |
inc-file-names --help | |
# ------------------------------- | |
# An example of using the script: | |
# ------------------------------- | |
# Create a temporary directory to mess around in | |
mkdir /tmp/inctest | |
cd /tmp/inctest | |
# Set up a few sample files to test the script with | |
touch 1-hello 2-another-title 3-another-new-file 4-this-happened \ | |
5-check-that-out 6-very-cool 7-yeah-that-exists 8-seriously 9-the-end | |
# Add a new file in the middle of the list | |
touch 3-just-adding-another-file | |
# Increment the files (remove the -n to not do a dry run) | |
inc-file-names . -i 3 -p 2 -n | |
# Remove the temporary directory | |
rm -rf /tmp/inctest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# (c) 2021 - Nick Janetakis <nick.janetakis@gmail.com> | |
# This code is licensed under the MIT license | |
import argparse | |
import os | |
import textwrap | |
def check_path(path): | |
if os.path.exists(path): | |
return path | |
else: | |
msg = f'path does not exist: "{path}"' | |
raise argparse.ArgumentTypeError(msg) | |
def check_positive_int(val): | |
int_val = int(val) | |
if int_val >= 0: | |
return int_val | |
else: | |
msg = f'must be a positive integer: "{val}"' | |
raise argparse.ArgumentTypeError(msg) | |
def get_files(dirname): | |
sorted_files = sorted(os.scandir(dirname), key=lambda e: e.name) | |
return [f.name for f in sorted_files if f.is_file()] | |
def inc_by(current_index, start_index): | |
""" | |
When adding a new item, you'll end up with a duplicate number before the | |
renaming is done. For example, let's say we have: | |
01-hi | |
02-cool | |
03-bye | |
And now we want to insert a new item between 02 and 03, as such: | |
01-hi | |
02-cool | |
02-something-else | |
03-bye | |
We need to increment everything by 1 except when the current file in the | |
loop matches the number we want to start renaming from. | |
Using the above example, the logic below produces files named like this: | |
01-hi | |
02-cool | |
03-something-else | |
04-bye | |
This is a pretty limited approach as it only supports adding 1 new file at | |
a time. If you added let's say 3 new items at once and wanted to re-index | |
everything this function won't properly increment the files. | |
""" | |
amount = 1 | |
if current_index == start_index: | |
amount = 0 | |
return amount | |
def increment_files(source_path, start_index, delimiter, pad_len, dry_run): | |
files = get_files(source_path) | |
for index, file in enumerate(files, 1): | |
file_name = os.path.basename(file) | |
# Let's skip files that don't have the delimiter. | |
if delimiter not in file_name: | |
continue | |
# We only want to split once in case the delimiter is used elsewhere | |
# in the file name, ie. 01-hello-world with a - delimiter. | |
file_name_parts = file_name.split(delimiter, 1) | |
prefix = file_name_parts[0] | |
name = file_name_parts[1] | |
# Let's also ignore any files that happen to contain the prefix but | |
# aren't a numbered file, such as: hey-this-file-should-be-skipped | |
try: | |
prefix_int = int(prefix) | |
except ValueError: | |
continue | |
# Only increment the files we want to. | |
increment_amount = 0 | |
if prefix_int >= start_index: | |
increment_amount = inc_by(index, start_index) | |
# zfill pads the string with N number of characters. | |
new_prefix = str(prefix_int + increment_amount).zfill(pad_len) | |
new_file_name = f'{new_prefix}{delimiter}{name}' | |
if dry_run: | |
print(f'{file_name} -> {new_file_name}') | |
else: | |
os.rename(file_name, new_file_name) | |
return None | |
def parseargs(): | |
parser = argparse.ArgumentParser( | |
formatter_class=argparse.RawDescriptionHelpFormatter, | |
description=textwrap.dedent('''\ | |
Increment file prefixes starting at a specific index.''')) | |
parser.add_argument('path', default=None, | |
metavar='PATH', type=check_path, | |
help='source path to look for files') | |
parser.add_argument('-i', '--index', type=check_positive_int, | |
metavar='VAL', required=True, | |
help='start incrementing at this prefix number') | |
parser.add_argument('-d', '--delimiter', type=str, default='-', | |
metavar='str', | |
help='prefix delimiter (defaults to a hyphen)') | |
parser.add_argument('-p', '--pad-len', type=check_positive_int, | |
metavar='VAL', required=True, | |
help='pad prefixes by this amount of zeros') | |
parser.add_argument('-n', '--dry-run', action='store_true', | |
help='print the results instead of renaming the files') | |
args = parser.parse_args() | |
return args | |
if __name__ == '__main__': | |
args = parseargs() | |
increment_files(args.path, args.index, args.delimiter, args.pad_len, | |
args.dry_run) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment