1

I am working on a script to run on my Synology DS 1019+ that takes all the subfolders within a certain directory, creates the subfolders in another directory, then creates hard links of all the .mkv files within the subfolders in the newly created subfolders in the other location. However, if the subfolder already exists in the second location, then I want it to just create the hard link.

Due to the nature of my files, the folder structure and files within said folders will be different for each instance this script will be used for. Is there a way to make this script loop so that each loop takes the next folder within Location A and performs the mkdir of each subfolder and hard link task (to the files within said subfolder)?

Here is what I have currently:

#! /bin/bash

echo "Enter Movie Collection:"
read MOVIE_DIR
echo "Enter Bonus Feature Disc: "
read BONUS_DIR

cd "/volume1/Plex/Movies/$MOVIE_DIR/"

for dir in */.; do

    if [[ ! -e "$dir"/*/ ]]; then
        mkdir "$dir"/*/
    fi

    if [[ ! -d "$dir"/*/ ]]; then
        ln /volume1/Plex/"Bonus Feature Discs"/$BONUS_DIR/*/*.mkv -t ./"$dir"/*/.
    fi
done

I am not skilled at all when it comes to loops, especially nested loops, so I am not sure where to begin to troubleshoot this. Currently, instead of the folders in location A being replicated (if not already existing) in Location B, and the files within the folders in Location A being hardlinked, I am getting an empty folder with a name of "_2X68P~X" within the Location B's main directories (for example, in my test, Instead of "TEST 1" getting a "featurette" folder with a hardlinked TEST.mkv within it, I just get that garbage data folder within "TEST 1" that is empty.

I have attempted to use basename as well as dirname, but I have yet to find a way to have it loop so that it cycles through each folder within a directory. I have also attempted to use cd /the/directory/path/ "${PWD##*/}"

EDIT: Over the course of the several hours after posting this, I did figure out a solution. The code I had above simply was not logically sound, thanks to too many areas that were not specific, which meant nothing would happen. I ended up having to restart from the ground up. Here is the code that I ended up with, and this code does do the job I need it to do. It might not be the most elegant method for doing this, but it does work well enough based on the tests I've ran.

#! /bin/bash

#Ask the user to input the directories of both the bonus disc's files and where the movies are located that the bonus disc's contents will be needed.
echo "Enter the name of the Movie Collection and press [ENTER]: "
read MOVIE_DIR
echo "Enter the name of the Bonus Feature Disc and press [ENTER]: "
read BONUS_DIR


#This goes to the location of the bonus disc specified by the end user. I believe this part is necessary for creating the text document below, but it might not be.
cd "/volume1/Plex/Bonus Feature Discs/$BONUS_DIR/" || return

#This creates a text document that has each directory within the specified Bonus Disc directory as a separate line  
    ls -d -- */ >> /volume1/Plex/"Bonus Feature Discs"/output.txt
    echo ls  -d -- */


#This goes to the movie directory specified by the end user. This cd is definitely required
cd "/volume1/Plex/Movies/$MOVIE_DIR/" || return


#the for loop loops through every movie that resides in the movie collection folder
 for dir in *; do

    #this while loop reads the text document and will use each line from the document as a variable.
    while IFS=' ' read -r line; do
        name="$line"

        #A directory with the name of the line of the text document will be created in each movies directory only if it doesn't already exist
        mkdir -p "$dir/$name"

        #this will create the hard links to every single video that resides in the folders within the bonus features disc into the corresponding folders for each movie
        if [[ ! -e "/volume1/Plex/Movies/$MOVIE_DIR/$name" ]]; then 
            ln "/volume1/Plex/Bonus Feature Discs/$BONUS_DIR/$name"*.mkv -t ./"$dir/$name"
        fi

    done < "/volume1/Plex/Bonus Feature Discs/output.txt"
 done

echo "Linking Completed"
echo $dir

#finally, once all the work is complete, the script will delete the text document that is no longer needed
rm "/volume1/Plex/Bonus Feature Discs/output.txt"

2 Answers 2

0

Using rsync, and assuming that you'd like to hard link all .mkv files in or under the source directory into the target directory, including creating all directories (even if they are empty of .mkv files).

rsync --archive --link-dest="$PWD/source" \
    --include='*/' \
    --include='*.mkv' \
    --exclude='*' \
    source/ target

The --archive (-a) option makes rsync preserve file metadata and also triggers recursive copying of directories. The --link-dest option gives rsync a directory from which existing files can be hard linked from into target. The --include options select directories and the .mkv files while the final --exclude option ignores everything not already selected with --include.

To delete empty directories that may be generated in target, you can use find like so:

find target -type d -empty -delete

... assuming your find implements the -empty test and the -delete action.

Example:

source
|-- dir1
|   |-- file1.mkv
|   |-- file1.txt
|   |-- file2.mkv
|   `-- file2.txt
|-- dir2
|   |-- file1.mkv
|   |-- file1.txt
|   |-- file2.mkv
|   `-- file2.txt
|-- dir3
|   |-- file1.mkv
|   |-- file1.txt
|   |-- file2.mkv
|   `-- file2.txt
`-- dir4
    `-- file1.doc

The rsync command above is run and target becomes...

target
|-- dir1
|   |-- file1.mkv
|   `-- file2.mkv
|-- dir2
|   |-- file1.mkv
|   `-- file2.mkv
|-- dir3
|   |-- file1.mkv
|   `-- file2.mkv
`-- dir4

Just showing that the files are hard linked (same inode number, link count is 2):

$ ls -l -i source/dir1/file1.mkv target/dir1/file1.mkv
3118217 -rw-r--r--  2 kk  kk  0 Apr 10 17:03 source/dir1/file1.mkv
3118217 -rw-r--r--  2 kk  kk  0 Apr 10 17:03 target/dir1/file1.mkv

Deleting empty directories:

$ find target -type d -empty -delete
$ tree target
target
|-- dir1
|   |-- file1.mkv
|   `-- file2.mkv
|-- dir2
|   |-- file1.mkv
|   `-- file2.mkv
`-- dir3
    |-- file1.mkv
    `-- file2.mkv
1
  • I actually ended up running into a solution myself that works. It might not be the most elegant solution, but what I did was essentially rebuild it from the ground up, adding a command to create a text file listing the directories, and replacing the two if statements with a while loop that uses a line from the file as a variable each iteration of the loop. Finally, I added a command to delete the text file once it was no longer needed. I definitely want to tinker around with it more and try this as well as a learning experience. Thank you for the response!
    – SilJeff
    Apr 11, 2020 at 4:05
0

I'll assume that you somehow assign values to the two variables dirA and dirB ("Location A" and "second location" in your description).

You loop on all the files in dirA.

Each time the file in dirA is a directory you create another directory with the same name on dirB. The --parent switch prevents mkdir from throwing an error if the new directory already exists, eliminating the need to check for existence of the new directory.

The inner if checks that there are .mkv files and skips the directory if not.

This script works for a single directory level: if you have directories inside directories, these will not be looked at.

    #! /bin/bash

    dirA=...
    dirB=...

    cd "$dirA"

    for dir in *; do
      if [ -d "$dir" ]; then
         names=$(find "$dir" -maxdepth 1 -name '*.mkv')
         if [ "$names" ]; then
           mkdir --parent "$dirB/$dir"
           ln "$dir"/*.mkv "$dirB/$dir"
         fi
      fi
    done
2
  • I tried to understand what the user's code would do, but it's too complex, so I gave up explaining what's wrong with it Apr 10, 2020 at 13:33
  • The code was broken at a fundamental level, which is most likely why it didn't make sense. I eventually had to start over, and I did find another method that works. Those if statements there had far too many variables that there was no way to know what anything meant. The first If statement was supposed to create directories in Location B that matched Location A if not already there. The second one would then create hard links in said folders that were new. The statements worked when I used a different script for each job, where the locations were hard coded.
    – SilJeff
    Apr 11, 2020 at 4:07

You must log in to answer this question.

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