-1

I have a lot of ogg or wave files in different folders that I want to sequentially number while keeping everything that stands behind the prefixed number. The input may look like this

Folder1/01 Insbruck.ogg
        02 From Milan to Rome.ogg
        03 From Rome to Naples.ogg


Folder2/01 From Naples to Palermo.ogg
        02 From Palermo to Syracrus.ogg
        03 From Syracrus to Tropea

The output should be:

Folder1/01 Insbruck.ogg
        02 From Milan to Rome.ogg
        03 From Rome to Naples.ogg

Folder2/04 From Naples to Palermo.ogg
        05 From Palermo to Syracrus.ogg
        06 From Syracrus to Tropea.ogg

The sequential numbering across folders can be done with this BASH script that I found here:

find .  | (i=0; while read f; do 
let i+=1; mv "$f" "${f%/*}/$(printf %04d "$i").${f##*.}"; 
done)

But this script removes the title that I would like to keep.

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223

3 Answers3

2

TL;DR

Like this, using and perl rename:

rename -n 's@/\d+@sprintf "/%0.2d", ++$::c@e' Folder*/*

Drop -n switch if the output looks good.

With -n, you only see the files that will really be renamed, so only 3 files from Folder2.


Going further

The variable $::c (or $main::c is a package variable) is a little hack to avoid the use of more complex expressions:

rename -n 's@/\d+@sprintf "/%0.2d", ++our $c@e' Folder*/*

or

rename -n '{ no strict; s@/\d+@sprintf "/%0.2d", ++$c@e; }' Folder*/*

or

rename -n '
    do {
        use 5.012;
        state $c = 0;
        s@/\d+@sprintf "/%0.2d", ++$c@e
    }
' Folder*/*

Thanks go|dfish & Grinnz on freenode

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • 1
    I tested your first rename one liner without the export c=0 and it worked very well. The nice thing is that both solutions work folder-sensitive: so I can run the script on different folder names: that is very useful as the name of the folders contain the name of the works that I sequentially want to number. But there might be more than one work - lets say "Hamlet1 ... 4" and "Othello ..1..4" and like this I can run the script seperately. Thanks a lot. – PeterStrawson Jun 30 '20 at 20:53
1

A bash script for this job would be:

#!/bin/bash

argc=$#
width=${#argc}

n=0
for src; do
    base=$(basename "$src")
    dir=$(dirname "$src")
    if ! [[ $base =~ ^[0-9]+\ .*\.(ogg|wav)$ ]]; then
        echo "$src: Unexpected file name. Skipping..." >&2
        continue
    fi
    printf -v dest "$dir/%0${width}d ${base#* }" $((++n))
    echo "moving '$src' to '$dest'"
#   mv -n "$src" "$dest"
done

and could be run as

./renum Folder*/*

assuming the script is saved as renum. It will just print out source and destination file names. To do actual moving, you should drop the # at the beginning of the line # mv -n "$src" "$dest" after making sure it will work as expected. Note that the mv command will not overwrite an existing file due to the -n option. This may or may not be desirable. The script will print out a warning message and skip unexpected file names, that is, the file names not fitting the pattern specified in the question.

M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17
  • This script version works very conveniently as well: one thing though: is it possible to adjust the "%04d" - Parameter dynamically to the number of files? So that it is "%04d" for less than 10000 "%03d" for less than 1000 "%02d" for less than 100? – PeterStrawson Jul 01 '20 at 18:17
  • @PeterStrawson Yes, I edited the script for that. `argc` is the argument count and `width` is the number of decimal digits in `argc`. But this simple approach may fail if there are file names not fitting the pattern (file names which would issue the warning message in the script) in the argument list. The script may also suffer from the"argument list too long" error. – M. Nejat Aydin Jul 01 '20 at 22:38
0

The sequential numbering across folders can be done with this BASH script that I found here:

find .  | (i=0; while read f; do 
let i+=1; mv "$f" "${f%/*}/$(printf %04d "$i").${f##*.}"; 
done)

But this script removes the title that I would like to keep.


Not as robust as the accepted answer but this is the improved version of your script and just in case rename is not available.

#!/usr/bin/env bash

[[ -n $1 ]] || {
  printf >&2 'Needs a directory as an argument!\n'
  exit 1
}

n=1
directory=("$@")

while IFS= read -r files; do
  if [[ $files =~ ^(.+)?\/([[:digit:]]+[^[:blank:]]+)(.+)$ ]]; then
    printf -v int '%02d' "$((n++))"
    [[ -e "${BASH_REMATCH[1]}/$int${BASH_REMATCH[3]}" ]] && {
      printf '%s is already in sequential order, skipping!\n' "$files"
      continue
    } 
    echo mv -v "$files" "${BASH_REMATCH[1]}/$int${BASH_REMATCH[3]}"
  fi  
done < <(find "${directory[@]}" -type f | sort )

Now run the script with the directory in question as the argument.

./myscript Folder*/

or

./myscript Folder1/

or

./myscript Folder2/

or a . the . is the current directory.

./myscript .

and so on...

  • Remove the echo if you're satisfied with the output.
Jetchisel
  • 7,493
  • 2
  • 19
  • 18
  • But this seems to contain the name of the folder within the script. And that means that one needs to change the script for every usage. M. Nejat Aydin solution allows to call the folders name while executing the script. Or am I missing something here? – PeterStrawson Jul 01 '20 at 18:41
  • Thank you for that wonderful comment, I have updated the answer to give the directory/path where the files are as an arguments. – Jetchisel Jul 01 '20 at 19:59