5

I have a webdav server containing thousands of files:

└── Sync
     ├── 20180719_120823.jpg
     ├── 20180719_120933.jpg
     ├── 20190719_120955.jpg
     ├── 20190719_121023.jpg
     ├── 20190719_121032.jpg
     ├── 20190720_121037.jpg
     ├── 20190721_120823.mp4
     ├── 20190822_220013.jpg
     └── 20190822_230155.mp4
                 "
                 "

The first part of file name is YYYYMMDD. I want to use this to copy files to another drive using below structure. Bonus if MM is translated to name of month...:

├── 2018
│    └── Jul
│         ├── 20180719_120823.jpg
│         └── 20180719_120933.jpg
└── 2019 
     ├── Jul
     │    ├── 20190719_120955.jpg
     │    ├── 20190719_121023.jpg
     │    ├── 20190719_121032.jpg
     │    ├── 20190720_121037.jpg
     │    └── 20190721_120823.mp4
     └── Aug
          ├── 20190822_220013.jpg
          └── 20190822_230155.mp4

I can manually create the year/month structure. But it would be nice if the directories are created if it doesn't exist. The script will run once/day and if file already exist, we skip it.

carnock
  • 121

3 Answers3

10

Something like this can be done in Bash:

Where you have:

$ tree Sync/
Sync/
├── 20180719_120823.jpg
├── 20190719_120823.jpg
├── 20190719_120920.jpg
├── 20190720_121037.jpg
├── 20190822_120823.jpg
└── 20190822_120823.mp4

1 directory, 6 files

Then, you do:

$ for f in Sync/*
  do
  if [ -f "$f" ]
    then
    n="${f/*\//}" # Get filename
    y="${n:0:4}" # Get Year part
    m="${n:4:2}" # Get Month part
    mn="$(date -d "$m/01/2000" "+%b")" # Translate Month number to name
    mkdir -p Dest/"${y}/${mn}" # Create destination directories if they don't exist
    cp -nv -- "$f" Dest/"${y}/${mn}/"
    fi
  done
'Sync/20180719_120823.jpg' -> 'Dest/2018/Jul/20180719_120823.jpg'
'Sync/20190719_120823.jpg' -> 'Dest/2019/Jul/20190719_120823.jpg'
'Sync/20190719_120920.jpg' -> 'Dest/2019/Jul/20190719_120920.jpg'
'Sync/20190720_121037.jpg' -> 'Dest/2019/Jul/20190720_121037.jpg'
'Sync/20190822_120823.jpg' -> 'Dest/2019/Aug/20190822_120823.jpg'
'Sync/20190822_120823.mp4' -> 'Dest/2019/Aug/20190822_120823.mp4'

To get:

$ tree Dest/
Dest/
├── 2018
│   └── Jul
│       └── 20180719_120823.jpg
└── 2019
    ├── Aug
    │   ├── 20190822_120823.jpg
    │   └── 20190822_120823.mp4
    └── Jul
        ├── 20190719_120823.jpg
        ├── 20190719_120920.jpg
        └── 20190720_121037.jpg

6 directories, 6 files


Notice that the date command used above to translate month numbers to month names will do so according to the default locale (i.e. language) on your system, but you can change that to another language/locale (needs to be already installed on your system) by temporarily setting the LC_ALL environment variable on the same line and directly before the date command like for example to translate month numbers to Swedish month names, you'd change the above related line from:

mn="$(date -d "$m/01/2000" "+%b")" # Translate Month number to name

To:

mn="$(LC_ALL=sv_SE.utf8 date -d "$m/01/2000" "+%b")" # Translate Month number to Swedish name

.. which will for example translate the month number 10 to the Swedish short name okt. If you want the full name, you can change "+%b" to "+%B" which will respectively translate to oktober.

Raffa
  • 34,963
10

Here's one way:

declare -A months=( 
 [01]="Jan" 
 [02]="Feb" 
 [03]="Mar" 
 [04]="Apr" 
 [05]="May" 
 [06]="Jun" 
 [07]="Jul" 
 [08]="Aug" 
 [09]="Sep" 
 [10]="Oct" 
 [11]="Nov" 
 [12]="Dec"
)

for file in Sync/; do year=$(cut -c 1-4 <<<"${file##/}") month=$(cut -c 5-6 <<<"${file##*/}") mkdir -p "$year/${months[$month]}" cp -n -- "$file" "$year/${months[$month]}" done

First we create an associative array linking numbers to month names. Then, for every file in the Sync directory, we use cut to extract the year and month (see https://tldp.org/LDP/abs/html/string-manipulation.html for an explanation of the ${file##*/} format). Then, mkdir -p to create the directory if it doesn't exist, and then we move the file. The -n option to cp makes sure that existing files are not overwritten (with thanks to @Raffa who added it!).

terdon
  • 104,119
6

Thank's for input... this was fun !
I ended up with a combination of both suggestions above.

#! /bin/bash

Declare monts in Swedish

declare -A months=( [01]="Januari" [02]="Februari" [03]="Mars" [04]="April" [05]="Maj" [06]="Juni" [07]="Juli" [08]="Augusti" [09]="September" [10]="Oktober" [11]="November" [12]="December" )

for f in /mnt/4TB/apa/* do if [ -f "$f" ]; then filename="${f##*/}" # Get the base filename

# pick the first 9 characters
first_nine=&quot;${filename:0:9}&quot;

# Use regular expression to check if the first nine characters are 8 digits followed by an underscore
if [[ &quot;$first_nine&quot; =~ ^[0-9]{8}_ ]]; then
       n=&quot;${f/*\//}&quot; # Get filename
       y=&quot;${n:0:4}&quot; # Get Year part
       m=&quot;${n:4:2}&quot; # Get Month part
       mn=&quot;${months[$m]}&quot; # Get Month in Swedish
       mkdir -p /mnt/4TB/bepa/&quot;${y}/${mn}&quot; # Create destination directories if they don't exist
       cp -np -- &quot;$f&quot; /mnt/4TB/bepa/&quot;${y}/${mn}/&quot; # Copy file
fi

fi done

terdon
  • 104,119
carnock
  • 121