0

I am trying to create a bash script that iterates a text file wich contains a list of folder names, and checks if any of these folders are missing from a certain path, in this case a directory called 2021.

My list.txt file contains a list like this one:

100001
100002
100003
100004
...

and I have a directory (the so called 2021) that contains sub-folders whos names I want to check

100001
100002
100004
...

Now, in this example, the script should state that folder 100003 is missing. The purpose of the script is just to find which of the folders listed in the text file are missing from the path

So far, I have:

while IFS= read -r line; do
    if [[ ! -d "2021/$line" ]]; then
        echo $line is missing
    fi
done <<< $(cat list.txt)

but this code just prints all the folders that exist inside 2021 directory, so I am guessing that there is something wrong inside the IF statement, but I can't figure this out.

3
  • Welcome, where is the script placed? Some notes: 1. You can just use done < list.txt. 2. Use full paths instead of relative ones, example: "/home/user/2021/$line". Commented Nov 26, 2022 at 3:23
  • @schrodingerscatcuriosity Thankyou. I've also tried with absolute paths, but the result is exactly the same. Please see my comment below Edgar Megallons' post. Commented Nov 26, 2022 at 3:47
  • Check blanks after the lines in file.txt. Commented Nov 26, 2022 at 4:14

3 Answers 3

0

I assume you are running the shell script outside the path which contains 2021 folder.
Having this structure:

$> tree
#output: 
.
├── 2021
│   ├── 100000
│   ├── 100001
│   ├── 100002
│   ├── 100003
│   ├── 100006
│   ├── 100007
│   ├── 100008
│   ├── 100009
│   ├── 100010
│   ├── 100011
│   ├── 100019
│   ├── 100020
├── list.txt
└── script

You should be able to run your current script without changing anything by using simply: ./script

The problem is that if you run script outside the script directory, the code will not work successfully. So I suggest you change your script to this:

#!/bin/bash

filepath="$(dirname $(readlink -f $0) )"
cd $filepath

while read -r line; do
    if [[ ! -d "2021/$line" ]]; then
        echo $line is missing
    fi
done <<< $(cat list.txt)
#Or:
#  done < /paht/to/list.txt

I removed IFS= . I'm not sure why you have it, but if you really have to use then just add it

So you will be able to run your script from any directory, e.g. ./path/to/dir/script


You can also get the missing directories by using the following command:

#With this code you will get only the missing dirs. The text 'is missing'
#won't appear here
diff --new-line-format="" --unchanged-line-format=""  <(sort /path/to/list.txt)  <(find . -maxdepth 1 -type d ! -path . -printf '%P\n' | sort)

If your file list.txt is actually sorted then you can change <(sort /path/to/list.txt) to simply /path/to/list.txt

If you use the code above make sure you are placed under 2021 directory as well as changing /path/to/list.txt to the actual path of list.txt

3
  • Thanks for your answer Edgar. The tree structure you showed in your post is exactly how it is on my end. My script and also the list.txt file are at the same level as 2021. So, one level down, I have all the sub-folders. I always run the script with ./ before the name. Unfortunatelly, none of the solutions have worked. The one liner diff command, also shows every folder as output. Commented Nov 26, 2022 at 3:45
  • I'm not really sure why none of the solutions worked for you. I hope someone knows what is actually happening. About "The one liner diff command, also shows every folder as output." it should not be that the behavior? In fact, (in my case) both scripts show the same missing folders. Commented Nov 26, 2022 at 3:51
  • 1
    The location of the script is of no interest - the place, from where it is invoked, relative to the 2021 folder is. Commented Nov 26, 2022 at 5:11
0

After hours searching for the problem, I've finally found the solution.

The problem was that the file was initially created under windows OS and then used in a bash shell, which means, in Windows lines end with both a line feed and a carriage return, but in Unix they end only with a single line feed.

So I am guessing that, when the IF compared the name in the file with the name in the folder, it obviously couldn't be a match, because the name inside the list file had an extra carriage return, invisible to any eye.

So I've recreated the file under unix, and now, Lo and Behold, I am getting the expected result flawlessly. Thanks to all who tried to help.

0

I using zsh instead of bash is an option, you could use its superior globs and its array conjunction (${A:*B}) and subtraction (${A:|B}) operators:

#! /bin/zsh -
list=path/to/list.txt
dir=path/to/2021
# or list=${1?} dir=${2?}
# for instance to pass those as arguments to the script

expected=( ${(f)"$(<$list)"} )
# or expected=( ${(f)"$(dos2unix < $list)"} )
# if that file is in MS DOS/Windows format

got=( $dir/*(ND/:t) )
# or got=( $dir/*(ND-/:t) )
# to also consider symlinks to directories like your [[ -d ... ]] does

report() {
  if (( $# > 1 )); then
    print -rC1 -- $1: ' - '$^@[2,-1]
   else
    print -r   -- $1: none
  fi
}

report Found      ${expected:*got}
report Missing    ${expected:|got}
report Unexpected ${got:|expected}
1
  • 1
    Not sure why this was down-voted. It's a good solution. I did not know that zsh is able to perform both conjunction and subtraction. Commented Nov 26, 2022 at 5:36

You must log in to answer this question.

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