user3916570
user3916570

Reputation: 778

Bash Regex rename files

I have a bunch of files that are simply just a unix time. The problem is that their filenames go to millis and the program that scans the directory expects them to be in seconds. So I need to rename all the files to remove the last 3 digits before the .txt extension. IE:

1461758015598.txt --> 1461758015.txt

I am new to regex and bash so I am not sure how to do this.

Upvotes: 3

Views: 1665

Answers (2)

Mark Reed
Mark Reed

Reputation: 95252

Here's one way:

for f in *.txt; do   
  mv "$f" "${f/[0-9][0-9][0-9].txt/.txt}"
done

Between the do and done, it's looking at one file at a time, and the current file's name is in the paramter f. "$f" is the value of that parameter, unchanged. "${f/[0-9][0-9][0-9].txt/.txt}" is the value of the parameter with a substitution applied: replace "three digits followed by .txt" with just .txt.

As @anubhava pointed out, if you run it more than once it will keep lopping digits off the filenames, so don't do that. You can make it idempotent by tightening up the glob pattern. This is a little unwieldy:

for f in [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].txt; do   
  mv "$f" "${f/[0-9][0-9][0-9].txt/.txt}"
done

So you might want to use an external tool to make it more compact (this assumes there are no spaces or newlines in any filenames in the directory):

for f in $(ls -1 | egrep '^[0-9]{13}\.txt$'); do 
  mv "$f" "${f/[0-9][0-9][0-9].txt/.txt}"
done

You can reduce the restriction to just "no newlines" by using a while read loop:

ls -1 | egrep '^[0-9]{13}\.txt$' | while read f; do
  mv "$f" "${f/[0-9][0-9][0-9].txt/.txt}"
done

Upvotes: 3

ghoti
ghoti

Reputation: 46846

Mark's answer covers a number of options.

But if you want something recursive, I'd go with a find based solution like this:

find -E . -regex '.*/[0-9]{13}\.txt' -exec \
  sh -c 'set -; mv -v "$0" "${0%???.txt}.txt"' "{}" \;

Notes:

  • I'm using FreeBSD, which has a -E option to tell -regex to use ERE. If you're using an older OS, you might need to convert the ERE to BRE.
  • The ERE insists on 13 digits. This saves you from anubhava's concern about increasing diminishment if you run the conversion more than once.
  • For parameter expansion I'm using ${..%..} instead of ${../../}, making this POSIX compliant rather than dependent on bash. (It'll work in bash too of course, it just doesn't need to.)
  • You might wonder why the match simply strips single wildcard characters instead of digits. This is fine - easier for me to type, and we've already set the filename constraint as part of the options to find.

If you'd prefer to avoid using find but still want something recursive in bash, you could use the globstar option:

shopt -s globstar extglob
for f in **/+([0-9]).txt; do
  [[ $f =~ ^[0-9]{13}\.txt$ ]] && mv -v "$f" "${f%...\.txt}.txt"
done

Note that globstar depends on bash version 4.

Upvotes: 0

Related Questions