Home > Net >  Rename all files in directory using bash by decrementing filename by 1
Rename all files in directory using bash by decrementing filename by 1

Time:11-03

Is there a way to rename a directory full of files named with the following convention, without using regex? For instance, I have files named 1.json, 2.json, 3.json, 4.json and I want them all renamed to their current #{NUM} to #{NUM - 1} so the result set would be: 0.json, 1.json, 2.json, 3.json.

Using the convention:

for i in *.json; do mv -- "$i" ..

Is it possible to do this without regex? Or am I stuck parsing for the file name, converting a string to integer and going from there?

CodePudding user response:

${var%suffix} will remove a trailing suffix. You can use it to strip off .json and get just the numbers.

for file in *.json; do
    i="${file%.json}"
    echo mv -- "$i".json "$((i-1))".json
done

Remove the echo if it looks good.


Note that this will only handle single digit file names. To handle multi-digit file names we need to have them sorted naturally rather than lexicographically. Lexicographic sorting puts 10 before 9 since the character 1 is less than 9, meaning 10.json will get renamed to 9.json before 9.json is renamed to 8.json. Natural sorting, by contrast, will sort 9 before 10.

Sadly, this makes the whole thing ten times more cryptic. Globs can't be sorted inline so we have to move things around so that we can pipe the list of file names to sort -g. Then to loop over a stream of file names we have to change from a for loop to a while read loop with process substitution.

The basic skeleton structure looks like this:

while read file; do
    ...
done < <(ls *.json | sort -g)

Then we'll layer on a few improvements:

  1. Use while IFS= read -r to make the parsing more robust.

  2. Avoid parsing ls by switching to a printf-based technique to print the file names.

  3. Use NUL separators instead of newlines, since newlines are technically legal characters in file names. That means adding \0 to printf, -z to sort, and -d $'\0' to read to get each of the three to emit and consume NULs.

These steps are good general habits, but I'll admit they're not terribly important for this particular script, and they make the code look like Frankenstein's monster. I'm showing them here for pedagogical purposes. I won't tell if you remove them from your one-off personal use script.

Final, robust solution:

while IFS= read -rd $'\0' file; do
    i="${file%.json}"
    echo mv -- "$i".json "$((i-1))".json
done < <(printf '%s\0' *.json | sort -zg)

CodePudding user response:

You may use this awk based solution:

printf '%s\n' [1-9]*.json | sort -n | 
awk -F. '{print "mv", $0, ($1-1 FS $2)}' | bash

mv 1.json 0.json
mv 2.json 1.json
mv 3.json 2.json
mv 4.json 3.json
mv 5.json 4.json

Once you're happy with the output you can remove echo before mv.

CodePudding user response:

try this code. simpy by -1 from filename. but if your filename contains zero at first name, lets say 001.json ..n then there is 1.json , this code will not work. because it will replace the current 1.json file

declare -i newName=0
for i in `ls | sort -n | grep ".json"`
do
  newName=${i%%.*}-1
  mv -- "$i" "$newName.json"
done
  • Related