So, say, I have a directory with a bunch of files like g.txt, where g.txt was last modified on, say, June 20, 2012.

How would I batch-rename all of the files (like g.txt) with the last modified date of June 20, 2012 appended on the end?


Quick-and-dirty Bash one-liner to rename all (globbed) files in the current directory from filename.txt to filename.txt-20120620:

for f in *; do mv -- "$f" "$f-$(stat -c %Y "$f" | date +%Y%m%d)"; done

An enterprising Bash nerd will find some edge case to break it, I'm sure. :)

Obviously, this doesn't do desirable things like checking whether a file already has something that looks like a date at the end.

  • 2
    +1, looks good. you can make it a little more robust by using '--' as the first arg to mv. guards against filenaes begining with '-' – cas Jul 18 '12 at 3:28
  • 5
    What kind of date implementation is that? My GNU date doesn't seem to handle input. – manatwork Jul 19 '12 at 7:33
  • coreutils 8.4 on RHEL 6. – jgoldschrafe Jul 19 '12 at 12:37
  • 2
    @jgoldschrafe, are you sure that code properly works for you? pastebin.com/nNYubf3A – manatwork Jul 19 '12 at 15:00
  • 5
    good catch manatwork. my GNU date 8.13 doesn't support reading from stdin either. It does support using the mod time of a file with "-r" - e.g. "date +%Y%m%d -r $f", so stat isn't even needed in this particular case. – cas Jul 20 '12 at 0:45

here's a version of goldschrafe's one-liner that:

  • doesn't use stat
  • works with earlier versions of GNU date
  • correctly copes with any spaces in the filenames
  • also copes with filenames beginning with a dash

    for f in *; do mv -- "$f" "$f-$(date -r "$f" +%Y%m%d)"; done

  • 1
    You should make it date -r "$f" +%Y%m%d or it won't work if POSIXLY_CORRECT is in the environment. Generally, options should go before other arguments. – Stéphane Chazelas Sep 1 '15 at 9:44
  • this should be the "correct answer" – invalidusername Mar 2 '16 at 9:10
  • Of course you don't need the braces in "${f}". – G-Man Mar 24 '17 at 2:57
  • but how would you make it such that the dates we prepended e.g "Ymd-filename" ? – timebandit Feb 10 '18 at 9:30

Obligatory zsh one-liner (not counting the one-time loading of optional components):

zmodload zsh/stat
autoload -U zmv
zmv -n '(*)' '$1-$(stat -F %Y%m%d +mtime -- $1)'

We use the stat builtin from the zsh/stat module, and the zmv function to rename files. And here's an extra which places the date before the extension, if any.

zmv -n '(*)' '$1:r-$(stat -F %Y%m%d +mtime -- $1)${${1:e}:+.$1:e}'

As I understood we don't know beforehand what is the modification date. So we need to get it from each file, format the output and rename each file in a way so that it includes the modification date in the filenames.

You can save this script as something like "modif_date.sh" and make it executable. We invoke it with the target directory as the argument:

modif_date.sh txt_collection

Where "txt_collection" is the name of the directory where we have all the files that we want to rename.


# Override any locale setting to get known month names
export LC_ALL=c
# First we check for the argument
if [ -z "$1" ]; then
    echo "Usage: $0 directory"
    exit 1

# Here we check if the argument is an absolute or relative path. It works both ways
case "${1}" in
  /*) work_dir=${1};;
  *) work_dir=${PWD}/${1};;

# We need a for loop to treat file by file inside our target directory
for i in *; do
    # If the modification date is in the same year, "ls -l" shows us the timestamp.
    # So in this case we use our current year. 
    test_year=`ls -Ggl "${work_dir}/${i}" | awk '{ print $6 }'`
    case ${test_year} in *:*) 
        modif_year=`date '+%Y'`
    # The month output from "ls -l" is in short names. We convert it to numbers.
    name_month=`ls -Ggl "${work_dir}/${i}" | awk '{ print $4 }'`
    case ${name_month} in
            Jan) num_month=01 ;;
            Feb) num_month=02 ;;
        Mar) num_month=03 ;;
        Apr) num_month=04 ;;
        May) num_month=05 ;;
        Jun) num_month=06 ;;
        Jul) num_month=07 ;;
        Aug) num_month=08 ;;
        Sep) num_month=09 ;;
        Oct) num_month=10 ;;
        Nov) num_month=11 ;;
        Dec) num_month=12 ;;
        *) echo "ERROR!"; exit 1 ;;
    # Here is the date we will use for each file
    modif_date=`ls -Ggl "${work_dir}/${i}" | awk '{ print $5 }'`${num_month}${modif_year}
    # And finally, here we actually rename each file to include
    # the last modification date as part of the filename.
    mv "${work_dir}/${i}" "${work_dir}/${i}-${modif_date}"
  • 1
    read the man page for stat(1) to find out how you can shrink your script by about 3/4...most of the above is re-inventing the wheel. in particular, 'stat -c %Y' will give you the modification time of a file in seconds since the epoch (1970-01-01 00:00:00). This can then be used as input to date(1) to format the timestamp as required. see jgoldschrafe's answer above for an example. – cas Jul 18 '12 at 5:56
  • @CraigSanders The upside is that this script works under operating systems other than Linux, which have a different stat utility or none at all. – Gilles Jul 19 '12 at 0:42
  • Note that parsing the output of ls is not reliable. On some unix variants, user and group names can contain spaces, which will throw off the alignment of the date columns. – Gilles Jul 19 '12 at 0:48
  • @giles: true, but anyone sensible :-) would install GNU coreutils (and all of the other GNU tools) on any non-linux system. It's the easiest way to get not only a good set of userland tools, but also a consistent set regardless of the underlying kernel. – cas Jul 19 '12 at 4:54
  • 1
    (Cont’d) …  (5) Another bug: it assumes that any date/time displayed by ls as a date and a time (rather than a date and a year) must be in the current year.  This is not true.  It is in the past six months; that could be in the prior year (e.g., 27-Sept-2016 through 31-Dec-2016 are within the past six months).  (6) At the risk of parroting Greg’s Wiki (which @Gilles already cited), filenames that contain newline(s) and spaces can cause this to fail. … (Cont’d) – G-Man Mar 27 '17 at 19:41

Here is version of cas's one-liner, (based on goldschrafe's oneliner) extended with idempotence,

i.e. extended to prefix files with date time and to do this only for those not having date time prefix yet .

Use-case: helpful if you add new files into directory and want to add date time prefix to those not having one yet.

for f in * ; do
  if [[ "$f" != ????-??-??' '??:??:??' - '* ]]; then
    mv -v -- "$f" "$(date -r "$f" +%Y-%m-%d' '%H:%M:%S) - $f" ;

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.