1

i have few folder:

1
2
3
4
5

so in each folder i have files like

00123.mp3
00133.mp3
00150.mp3

so i want to remove all 0 if they are at beginning of filename

i tried this

for file in *; do echo mv "$file" "${file//[ ()@0]/}"; done

but it remove all 0 in begenning and inside filename (i need just in begining) also this is not working in subdirectory

arlind
  • 13
  • 2
    Which shell are you using? bash with shell option extglob enabled supports "${file##+(0)}" to remove a leading sequence of one or more 0s I think – steeldriver Feb 07 '18 at 21:17
  • 1
    Note that it doesn't work in subdirectories because the asterisk wildcard is not recursive - it will always match items in the specified directory only. This is one thing all shells I'm aware of agree on. If you want to go into subdirectories, you need to figure out (and possibly enable) your shell's equivalent of globstar - likely some variation on **. – brhfl Feb 07 '18 at 21:24

3 Answers3

2

Since your question is tagged Linux, you could use rename or prename command (note that prename is not deprecated, though, but still works). In either case they're perl script for pattern-based renaming.

With group matching and using *.mp3 glob to pass only .mp3 files, you can do this:

$ prename  -nv 's/(^0*)(.*).mp3/$2.mp3/' *.mp3
Deprecated program in use: rename as shipped with the Debian perl package will be removed after the release of stretch. Please install the separate 'rename' package which will provide the same command.
000004500.mp3 renamed as 4500.mp3
00123.mp3 renamed as 123.mp3
00133.mp3 renamed as 133.mp3
00150.mp3 renamed as 150.mp3

Note that -n and -v are for dry run and verbose output; testing only. Remove -n for actual renaming to take effect.

In case you are confused about rename vs prename , you can read the corresponding answer that clears up the confusion, but...I would recommend that you just don't bother with that. Too convoluted :) The point is that in either case it works as shown, unless you're using ksh shell.


Recursive way to rename files in subfolders would be via find, however that requires a bit of acrobatics with substitution pattern to handle preceding items in the file path as provided by find.

find ./testdir/ -type f -name "*.mp3" -exec  prename  -nv 's/(.*)(\/0*)(.*).mp3/$1\/$3.mp3/' {} \;

The above command performed as follows in a test case:

$ find ./testdir/ -type f -name "*.mp3" -exec  prename  -nv 's/(.*)(\/0*)(.*).mp3/$1\/$3.mp3/' {} \;  2>/dev/null
./testdir/000004500.mp3 renamed as ./testdir/4500.mp3
./testdir/00150.mp3 renamed as ./testdir/150.mp3
./testdir/00133.mp3 renamed as ./testdir/133.mp3
./testdir/00123.mp3 renamed as ./testdir/123.mp3

Minor improvement could be to specify \d, the digit class:

find ./testdir/ -type f -name "*.mp3" -exec  prename  -nv 's/\/0*(\d*\.mp3)/\/$1/' {} \;

Stéphane Chazelas mentioned in the comments possibility where /0/1.mp3 could be accidentally rename to .//1.mp3. I've taken couple test cases; ./testdir/0/000987.mp3 renamed as ./testdir/0/987.mp3 and ./testdir/007/00123.mp3 renamed as ./testdir/007/123.mp3 were the results. I think greedy matching of 0* does the trick. He also suggested in the comments adding [^/] in filename pattern matching. This is a good idea since Unix filenames cannot have backslashes in them; paths can but not individual files. However, I am not quite sure how to better implement this and still have no suggestion on matching 0.mp3 or 000.mp3 types of filenames.

2

If you are using bash, this script will do what you ask for:

#!/bin/bash
shopt -s extglob
while IFS= read -d '' f ; do
    file=${f##*/}
    dir="${f%/*}/"
    echo \
    mv "$dir$file" "$dir${file##+(0)}"
done < <(find . -type f -name '0*.mp3' -print0)

comment the echo \ line to actually perform the mv command(s) once you agree with the list of files to move.

Or this sh version for portability:

#!/bin/sh

find . -type f -name '0*.mp3' -print |
while IFS= read f; do
    file=${f##*/}
    dir="${f%/*}/"
    f2=$file; until [ "$f2" = "${f2#0}" ]; do f2=${f2#0}; done
    echo \
    mv "$f" "$dir${f2}"
done
  • Note that mv inherits the stdin of the loop, so the pipe from find. -i would cause it to read the answer to confirmation prompts from the output of find. You could read the output of find on another fd (read <&3... done 3< <(find...). Or use mv -i < /dev/tty... – Stéphane Chazelas Feb 08 '18 at 00:15
  • @StéphaneChazelas Can not get the mv -i to work, just removed it. –  Feb 08 '18 at 01:56
1

With zsh:

autoload zmv # best in ~/.zshrc
zmv -n '0#(*.mp3)' '$1'

(remove -n (dry-run) when happy).

The # glob operator in zsh is like the * regular expression operator.

Note that it would rename 0000.mp3 to .mp3. To avoid that you can change it do:

zmv -n '0#([0-9]*.mp3)' '$1'

Recursively:

zmv -n '(**/)0#([0-9]*.mp3)' '$1$2'

To also remove the () @ characters as your example suggests you may want to do:

zmv -n '(**/)0#([0-9]*.mp3)' '$1${2//[() @]}'

(It would however not remove those characters from file names that don't start with a digit).