I have a folder which contains folders which contains files of the form


I want to remove the date and time for each of these, so in this case get


How do I do this in bash?


If you really want to do it "with bash", then

find . -name '*.wcr' -execdir bash -c '
  shopt -s extglob; for f; do echo mv -- "$f" "${f/_+([0-9-])_/_}"; done
' bash {} +

This relies on extended glob +([0-9-]) that matches one or more occurrences of characters in the set [0-9-]

You could make the replacement more specific e.g. ${f/_2017+([0-9-])_/_} if simply matching digits and dashes is too generic.

Note: remove the echo once you're certain that it's doing what you want.

  • If you really want to do it “with bash”, you might as well use **/ instead of find. It would be simpler. – Gilles May 27 '17 at 22:47

Use find + prename (Perl rename) commands :

find yourfolder -type f -name "*.wcr" -exec prename 's/^(.+)_[0-9]{4}-[0-9]{2}-[0-9]{2}[^_]+(_.*)$/$1$2/' {} +

To view prename result without action add -n option (print names of files to be renamed, but don't rename):

man rename

  • [0-9]{4}-[0-9]{2}-[0-9]{2} - pattern pointing to date-like substring (e.g. 2017-05-26)
  • That did not work. How can I say "recursively for my current working directory"? – Make42 May 27 '17 at 15:50
  • @Make42, ok, see my update now – RomanPerekhrest May 27 '17 at 15:59
  • You linked to the man page of the util-linux rename, but your code uses (one of) the perl rename. The util-linux rename cannot do what you need. The rename command is the perl rename only on Debian and derivatives. – Gilles May 27 '17 at 22:42

Using the perl rename command (either variant, but not the util-linux rename):

find . -type f -name \*.wcr -exec rename 's/_\d{4}-(\d{2}-){2}-(\d{2}-){3}\d+_/_/' {} +
for i in ./*/*; do
  j=`echo "$i" | cut -d'_' -f1-2,4-`
  mv "$i" "$j"

That's not the most elegant solution, but it works just fine, assuming that you want to rename all the files in subsubdirectories and they all match this pattern.


If the files are always one directory level deep, then you can iterate over them with something like for x in */*_20*_*. Which pattern to use depends on what other files might be present that you don't want to rename. The pattern I just gave assumes that the date starts with 20 and that all files whose name contains _20 and another undescore after that should be renamed.

You can do the renaming with a shell loop, using parameter expansion constructs to build the new file name.

for old_name in ./*/*_20_*_*; do
  base=${old_name##*/}        # remove the directory part
  prefix=${base%%_20*}        # remove everything from _20
  suffix=${base#*_20}         # remove everything up to _20
  suffix=${suffix#*_}         # ... then everything before the first remaining _
  mv "$old_name" "${old_name%/*}/${prefix}_${suffix}"

If the files are at varying depths, in bash ≥4.3, you can run shopt -s globstar then for x in **/*_20*_*; …. The pattern ** matches any directory depth if globstar is turned on. This also works in bash 4.0–4.2 with the caveat that it also traverses symbolic links to directories. This also works in zsh and ksh, without the caveat, out of the box in zsh and with set -o globstar in ksh.

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.