2

I have incorrectly named files which are unsynced by -1. The problem is I need to rename 1000s of them.

  • DBGC180_805754
  • DBGC180_805755
  • DBGC180_805756

to

  • DBGC180_805753
  • DBGC180_805754
  • DBGC180_805755

I would prefer using bash scripts or a unix command.

  • If you were on Mac OS X this would be a perfect use case for Name Mangler. – Wildcard Sep 29 '16 at 6:26
  • 1
    It's not 100% clear how the input files look like. Do all files have the same prefix DBGC180? Is 805753 the smallest number? Are there numbers with more or less than 6 digits? Are there numbers which shall not be renamed, e.g. <805753? – rudimeier Sep 29 '16 at 10:16
3
  1. Move the files to rename into a subdirectory (without changing their name).
  2. Rename the files from the subdirectory into the original directory.

There are two reasons I recommend step 1, even if it's possible to do without it:

  • If the command is interrupted, you can resume where you left off, since it's immediately obvious which files have already been renamed and which ones haven't.
  • You don't need to worry about doing the renaming in the wrong order and overwriting one of the existing files.

Untested shell snippet (relying on the fact that the number to decrement never has any leading zeros):

mkdir to_decrement
for x in DBGC180_80575[4-9] DBGC180_8057[6-9]? DBGC180_805[8-9]?? DBGC180_80[6-9]??? DBGC180_8[1-9]???? DBGC180_9?????; do
  mv "$x" to_decrement/
done
cd to_decrement
for x in *; do
  number="${x##*_}"
  mv -i -- "$x" "../${x%_*}_$((number-1))"
done
cd ..
rmdir to_decrement

With zsh, you can make this a lot simpler, thanks to its numeric range glob, its built-in mv which avoids running into command line length limits, and its pattern-based mass renaming function. In zsh:

autoload -U zmv
zmodload -m -F zsh/files b:zf_\*
mkdir to_decrement
zf_mv DBGC180_<805754-> to_decrement/
zmv 'to_decrement/(*)_(*)' '${1}_$(($2-1))'
rmdir to_decrement
  • I don't understand what is the sense behind your complicated globbing patterns. Why you don't want to rename say DBGC180_805750 but DBGC180_9yyyyy is included? Moreover the chance is high that there are too many files for one command line. – rudimeier Sep 29 '16 at 9:29
  • @rudimeier My understanding of the requirements is that only files with a number ≥805754 must be renumbered, and there are files with a smaller number that must not be renumbered. Good point about the command line length, I'll add a note about this. – Gilles Sep 29 '16 at 9:56
  • @rudimeier My understanding is that 805753 does not exist, but (at least some of) 805752, 805751, etc. do – Gilles Sep 29 '16 at 10:06
  • I see, comment to the question added. – rudimeier Sep 29 '16 at 10:18
2

You can do this:

# {smallestfilenum..largestfilenum}

for i in {805754..999999}; do 
   mv "DBGC180_$i" "DBGC180_$(($i-1))";
done

Try it with a small number (say 805754..805758) to make sure it works as you expect it. Be aware that if a file already exists with the new name, it will be overwritten.

  • 1
    Most 'mv' implementations have a -n option to prevent overwriting. Of course, if you use that, you'll then have the problem of not knowing which names were changed and which were the originals. I recommend renaming to a temporary directory, and checking the results before moving them back: mkdir tmp && for i in {805754..999999}; do mv "DBGC180_$i" "tmp/DBGC180_$(($i-1))"; done && mv -n tmp/* . && rmdir tmp Or mkdir tmp && mv DBGC180_* tmp/; for i in {805754..999999}; do mv -n "tmp/DBGC180_$i" "DBGC180_$(($i-1))"; done && rmdir tmp. If the rmdir fails, you need some manual fixing. – Toby Speight Sep 29 '16 at 10:44
0

So, you want to rename DBGC180_805754 to DBGC180_805753,...55 to ...54 and so forth. That's the problem I'll address.

First, put this script somewhere in your PATH, call it waltinator.

#!/bin/bash
#step through the parameters 
while [[ -n "$1" ]] ; do
    oldname="$1"
    # shift the arguments left
    shift;
    # strip off the fixed part of the old name
    oldnum=${oldname##DBGC180_}
    # decrement the number (this is what was wanted, right?)
    newnum=$(( $oldnum - 1 ))
    # build the new, improved filename
    newname="DBGC180_$newnum"
    if [[ -f "$newname" ]] ; then
        printf "Cannot rename $oldname to $newname, $newname exists.\n" >&2
        exit 1
    fi
    mv --no-clobber "$oldname" "$newname"
done
exit 0

For the next step, assume that the script is in $HOME/bin/waltinator, and you have does chmod +x $HOME/bin/waltinator.

find . -type f -name 'BDGC180_[0-9][0-9][0-9][0-9][0-9][0-9]` -print | \
    sort | \
    xargs $HOME/bin/waltinator

The find finds files (in no particular order), whose names match the shell glob pattern "BDGC180_ followed by 6 digits ([0-9]). Since we want a sorted list (it would be a failure to rename ...97 to ...96 before renaming ...96) we run the output of find through sort. Then we use xargs to take the (sorted) list of filenames, and build a command to pass the (sorted) list of filenames to $HOME/bin/waltinator. Read man xargs if you need to shorten the arg list.

For that matter, read:

for page in bash mv find sort xargs ; do
    man "$page"
done   
0

If you want to rename those specific files then here is the solution (static). First rename and move those files into sub directory. From there move those files to current directory

#!/bin/bash
# rename.sh

#make a subdirectory
mkdir -p subDir

#move all files to subdirectory with rename
for i in {5754..6754}; do
   mv "DBGC180_80$i" "./subDir/DBGC180_80$(($i-1))";
done

#move all files from subdirectory to current directory
for j in {5754..6754}; do
    mv "./subDir/DBGC180_80$(($j-1))" "./DBGC180_80$(($j-1))"
done

#remove subdirectory
rmdir subDir

This program can be modified to be generic (dynamic)

  • Huh? Why, in your "move from subdir to current dir" code block, do you even bother with j-1 (since you're doing it twice)? Why not just change the numbers used for iteration? – Wildcard Sep 29 '16 at 6:22
  • Of course, it can be done as you said. I kept it in a single line. – firoj_mujawar Oct 3 '16 at 7:49
0

Use rename to substitute each number with itself minus 1.

$ rename -v 's/\d{6}/sprintf("%06",($&-1))/e'    

Notes

  • rename uses Perl expressions to rename filenames.
  • s specifies that rename will substitute some or all of the filenames that match the regular expression pattern.
  • Perl substitution expressions are structured as such.
    • s/PATTERN/REPLACEMENT/MODIFIER
  • \d{6} is the pattern that rename will search for and substitute. This is 6 decimal digits.
  • $ is the variable storing the substring that matched \d{6} (in this case the "substring" is an int).
  • sprintf("%06",($&-1)) retrieves the value stored in $, decrements it, then returns this value as the replacement. The %06 is there to handle leading zeroes.
  • e specified that rename evaluates the replacement as if it were a Perl statement, and uses its return value as the replacement text.
  • -v makes rename echo what it is doing.

If you want to be extra safe use the flag -n so that rename only tells you what it would do instead of doing it.

This appears to only work for decrementing so handle this with care.

-1

Wow, this was trickier than I expected.

As Gilles pointed out, considering the number of files to be handled, you should rename them in a way that won't give you problems if the command is interrupted. The command below does that.

I wrote this command to be fairly robust against failures. Note that we explicitly exclude files where the number has a leading 0; if you have files like that, post a comment and I'll include a command to deal with them.

find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9]' -exec sh -c 'for f; do mv -i "$f" "${f%_*}_$((${f##*_}-1)).decremented"; done' find-sh-decrement {} + && find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9].decremented' -exec sh -c 'for f; do mv -i "$f" "${f%.decremented}"; done' find-sh-remove-prefix {} +

With line wrappings (can still be copied and pasted):

find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9]' \
  -exec sh -c 'for f;
    do mv -i "$f" "${f%_*}_$((${f##*_}-1)).decremented";
    done' find-sh-decrement {} + &&
  find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9].decremented' \
  -exec sh -c 'for f;
    do mv -i "$f" "${f%.decremented}";
    done' find-sh-remove-prefix {} +

Explanation of the parts (this could use some formatting cleanup):

##### Recursively find regular files in the current directory...
find . -type f
##### whose name matches this exact pattern...
-name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9]'
##### and run the following shell script...
-exec sh -c
##### (shell script: ) for every file given as an argument...
'for f;
##### rename the file, prompting for confirmation for any overwrites...
do mv -i
##### from the original file name...
"$f"
##### to the file name decremented by 1, with '.decremented' afterward...
"${f%_*}_$((${f##*_}-1)).decremented";
##### (End of shell script)
done'
##### on as many found files as possible at once,
##### using the name "find-sh-decrement" for error reporting.
find-sh-decrement {} +
##### If that completes successfully...
&&
##### Pass through again and remove the ".decremented" prefix.
find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9].decremented' -exec sh -c 'for f; do mv -i "$f" "${f%.decremented}"; done' find-sh-remove-prefix {} +

Test results:

$ ls
DBGC180_805754  DBGC180_805755  DBGC180_805756
$ cat DBGC180_805754
This file started as DBGC180_805754
$ cat DBGC180_805755 
This file started as DBGC180_805755
$ cat DBGC180_805756
This file started as DBGC180_805756
$ find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9]' -exec sh -c 'for f; do mv -i "$f" "${f%_*}_$((${f##*_}-1)).decremented"; done' find-sh-decrement {} + && find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9].decremented' -exec sh -c 'for f; do mv -i "$f" "${f%.decremented}"; done' find-sh-remove-prefix {} +
$ ls
DBGC180_805753  DBGC180_805754  DBGC180_805755
$ cat DBGC180_805753
This file started as DBGC180_805754
$ cat DBGC180_805754
This file started as DBGC180_805755
$ cat DBGC180_805755
This file started as DBGC180_805756
$ 

For extra safety, run the first find command and inspect the results for yourself before you run the second command to remove the .decremented suffix.

Run this:

find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9]' \
  -exec sh -c 'for f;
    do mv -i "$f" "${f%_*}_$((${f##*_}-1)).decremented";
    done' find-sh-decrement {} +

Then inspect results, and then run this:

find . -type f -name 'DBGC180_[1-9][0-9][0-9][0-9][0-9][0-9].decremented' \
  -exec sh -c 'for f;
    do mv -i "$f" "${f%.decremented}";
    done' find-sh-remove-prefix {} +
  • Why the downvote? This does exactly what was requested, and is quite clean in my opinion. – Wildcard Oct 3 '16 at 14:34
-1

Use awk to create a fully expanded rename script:

find . -name "DBGC180_*" |sort |awk -F "_" '{print "mv -i "$0" "$1"_"$2-1}' >/tmp/rename.sh

You can review the script before executing. This is the unique feature of this solution. Then execute it:

sh -e -x /tmp/rename.sh

Notes

  • you may improve the find pattern, the question is not 100% clear about what files has to be renamed.
  • sort is important to not overwrite existing files
  • mv option -i makes it extra-safe to never overwrite files
  • sh option -e is to abort the script in case of error
  • sh option -x prints a trace to see what was done so far in case the script is interrupted (to make recovery possible). You could also use mv -v instead if supported.
  • Down voters! Please comment whats wrong here. – rudimeier Sep 29 '16 at 11:12
  • Your added explanations are good, and the script may work fine for the exact use case of the Original Poster. However, it is definitely not robust. (My own definition of robust: Does not produce unexpected output in the face of unexpected input.) There are a great many caveats if the script would be modified and used more generally, and you don't note these: (1) It won't handle whitespace correctly, or even underscores, if they appear within directory names (i.e. not truly recursive); (cont'd) – Wildcard Sep 29 '16 at 19:51
  • (cont'd) (2) It won't handle file extensions (see what happens if you give it the file DBGC180_5555.txt, for instance); (3) If the script is interrupted partway through there is no way to tell which files have been renamed and which haven't been. – Wildcard Sep 29 '16 at 19:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.