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.

4
  • 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
    Commented Oct 6, 2023 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 [askubuntu.com/questions/483233/… and also: [askubuntu.com/questions/457705/…
    – carnock
    Commented Oct 6, 2023 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
    Commented Oct 6, 2023 at 20:42
  • One alternative is 2019/08-Aug/ Then it will be sorted in correct order...
    – carnock
    Commented Oct 7, 2023 at 10:08

3 Answers 3

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!).

0
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.

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="${filename:0:9}"

    # Use regular expression to check if the first nine characters are 8 digits followed by an underscore
    if [[ "$first_nine" =~ ^[0-9]{8}_ ]]; then
           n="${f/*\//}" # Get filename
           y="${n:0:4}" # Get Year part
           m="${n:4:2}" # Get Month part
           mn="${months[$m]}" # Get Month in Swedish
           mkdir -p /mnt/4TB/bepa/"${y}/${mn}" # Create destination directories if they don't exist
           cp -np -- "$f" /mnt/4TB/bepa/"${y}/${mn}/" # Copy file
    fi
  fi
done
5
  • 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
    Commented Oct 6, 2023 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
    Commented Oct 6, 2023 at 17:21
  • 2
    @Raffa point (1) has been debunked I think - see for example Is space allowed between #! and /bin/bash in shebang? Commented Oct 6, 2023 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
    Commented Oct 6, 2023 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
    Commented Oct 6, 2023 at 20:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .