I am working on a script to sort my downloads folder by the date the files were created. This is the script I am using:


cd ~/Downloads
for FILENAME in *
    if [[ -f $FILENAME ]]; then
        date="$(ls -l $FILENAME | tr -s ' ' | cut -d ' ' -f 6 | tr -d '.')"
        mkdir -vp "$date"
        find . -maxdepth 1 -type f -name $FILENAME -exec mv {} "$date" \;
cd -

The name of the folder created by this script is just Jun which leads me to believe there is some sort of conversion going on somewhere.

If I do just a

ls -l test0 | tr -s ' ' | cut -d ' ' -f 6 | tr -d '.'

the date shows 19062014 correctly.

This is the result of a bash -x of the script:

sigurd@Goliath ~ -> bash -x clean
+ cd /home/sigurd/Downloads
+ for FILENAME in '*'
+ [[ -f test0 ]]
++ ls -l test0
++ tr -s ' '
++ cut -d ' ' -f 6
++ tr -d .
+ date=Jun
+ mkdir -vp Jun
mkdir: created directory 'Jun'
+ find . -maxdepth 1 -type f -name test0 -exec mv '{}' Jun ';'
+ for FILENAME in '*'
+ [[ -f test1 ]]
++ ls -l test1
++ tr -s ' '
++ cut -d ' ' -f 6
++ tr -d .
+ date=Jun
+ mkdir -vp Jun
+ find . -maxdepth 1 -type f -name test1 -exec mv '{}' Jun ';'
+ cd -

The functionality I'm looking for is that the script should move files into folders named 19_06_2014, 20_06_2014 etc. based on when the file was created.


Wild guess: you have an alias for ls that's being used when you try the command interactively, and that alias isn't used in the script. Something like this, perhaps?

alias ls="ls --full-time"

Note that conventional wisdom says that trying to parse ls is a bad idea. Assuming you're on a Linux machine, something like this might be preferable:

stat "$FILENAME" | awk '/^Modify: /{print $2}'

EDIT: Or, as Stéphane points out below, using find with -printf is even better. The output of stat is slightly easier to predict than ls, but -printf allows for an output format that's entirely controlled by you.

  • Thanks, I figured it would be easier to use awk, just wasn't quite comfortable as to how. – slopedoo Jun 19 '14 at 19:39
  • 1
    Parsing the output of stat like that is hardly any better. GNU find can print the modification time in any format you like with its -printf. – Stéphane Chazelas Jun 19 '14 at 19:57

I would suggest using a date format like 2014-06-19. That way a ls would show them in chronological order.

On a GNU system:

#! /bin/bash -
cd ~/Downloads &&
  find . -maxdepth 1 -type f -printf '%p\0%TF\0' |
    while IFS= read -rd '' file && read -d '' date; do
      mkdir -vp "$date" && mv "$file" "$date"

With zsh on any system:

#! /bin/zsh
zmodload zsh/stat || exit

cd ~/Downloads &&
  for file (./*(ND.)) {
    zstat -A date -F %F +mtime $file &&
      mkdir -p $date &&
      mv $file $date
  • Hadn't thought about using -printf instead of stat - that's definitely an improvement. – godlygeek Jun 19 '14 at 21:22

Here's the complete code

cd ~/Downloads
for FILENAME in $(find . -maxdepth 1 -type f); do    
    DATE=$(stat "$FILENAME" | awk '/^Modify: /{print $2}')
    [ ! -d "$DATE" ] && mkdir -vp "$DATE"

cd -
  • 2
    Note that GNU find (you're already using a GNUism with -maxdepth) can do stat's job (and better) with -printf (find had -printf decades before GNU introduced its stat command). Using command substitution on the output of find means that it will break (possibly severely with security implications) on filenames contain blank or wildcard characters. You're not checking for succesful completion of cd or mkdir. – Stéphane Chazelas Jun 19 '14 at 19:55

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.