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
  • OK, so what part of this is giving you trouble? What do you have so far? Please show us what you have tried so we don't give you solutions that you know won't work. – terdon Oct 06 '23 at 11:47
  • I have been looking at the rename utility, but don't know how to use part of filename to create new path. Like this post [https://askubuntu.com/questions/483233/change-directory-structure-from-year-month-day-to-year-month-day/483594#483594] and also: [https://askubuntu.com/questions/457705/batch-files-renaming-w-filenames-shift/457710#457710] – carnock Oct 06 '23 at 12:06
  • 2
    If you can still change that, I would recommend not the 2019/Aug/ structure, but 2019/08/ (or maybe 2019-08/). That's likely much easier to work with in a variety of situations. It sorts well lexicographically, it's not case sensitive, it's not as much affected by localization. I understand if it's an existing structure you can't change; I figured I'd better share the advice anyway. – marcelm Oct 06 '23 at 20:42
  • One alternative is 2019/08-Aug/ Then it will be sorted in correct order... – carnock Oct 07 '23 at 10:08

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
  • 32,237
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
  • 100,812
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
  • 100,812
carnock
  • 121
  • A few things to note: 1) Breaking the shebang with space will render the path to the interpreter none-resolvable. 2) /bin/sh on Ubuntu is most likely linked to another shell e.g. Dash not Bash and your script needs the latter to run correctly, so use /bin/bash instead. 3) If you tell date to use Swedish with e.g. LC_ALL=sv_SE.utf8 date …, then it will do so. – Raffa Oct 06 '23 at 16:57
  • … enough with critique :-) .., You exhibit excellent coding capabilities and perfect parameter quoting with apparently quick learning gift (comparing your question to your answer in a short time frame) which I, personally, find outstanding. – Raffa Oct 06 '23 at 17:21
  • 2
    @Raffa point (1) has been debunked I think - see for example Is space allowed between #! and /bin/bash in shebang? – steeldriver Oct 06 '23 at 19:30
  • 1
    Thank you @Raffa.. I don´t take it as critique. I'm learning from it. I have been coding when I was younger, but this is actuallly my first bash script ever. Picking up pieces from here and there and putting it together. Great fun ! – carnock Oct 06 '23 at 20:03
  • @steeldriver I’m glad it’s been debunked … That and /bin/sh were holding my upvote to this answer and I wanted to :-) … Now +1 – Raffa Oct 06 '23 at 20:04