2

Let's say I have the following:

./
   Dir1/
      Sub1/
           Sub1.jpg
      Sub2/
           Sub2.jpg
           Sub2.png
           Sub2.txt
   Dir2/
      Sub1/
           Sub1.doc
           Sub1.txt
      Sub2/
           Sub2.jpg
           Sub2.png
           SomeTxt.txt
   Dir3/
      Dir3.txt
   Dir4/
      Sub1/
           Dir4.txt
      Some.txt

Using bash (bashrc in my case), how can I get a variable that contains all of the directories containing a text file (.txt) with the same name as the containing directory? So from the example above I would want to have a variable containing the following:

./Dir1/Sub2/    
./Dir1/Sub1/
./Dir3/

It shouldn't return ./Dir1/Sub1/ because it doesn't have a Sub1.txt file.

It shoudn't return ./Dir2/Sub2/ because the txt file in it isn't Sub2.txt.

It shouldn't return ./Dir4/ or ./Dir4/Sub1/ because Dir4.txt is not directly in ./Dir4/.

NOTE:

The names of the directories don't have to be these names in particular. What is important is that the txt file's name matches the containing directory's name.

Also, I know other languages might be easier but I need it in bash.

5
  • In Python: dirs = (path.parent for path in Path().rglob('*.txt') if path.parent.name == path.stem)
    – jfs
    Sep 28, 2017 at 3:43
  • It should be noted that dir3.txt and dir3 are not the same name. Sep 28, 2017 at 23:47
  • @andrea the name of the directory should match the name of the file without the extension. So because Dir3.txt is in the Dir3 directory, It would return ./Dir3/ Sep 28, 2017 at 23:56
  • @andrea I added the forward slash you each directory to help show that they are not files. Sep 29, 2017 at 0:03
  • What does this have to do with the bashrc?
    – wjandrea
    Oct 12, 2017 at 18:15

2 Answers 2

3

Given

$ tree somepath/
somepath/
├── Dir1
│   ├── Sub1
│   │   └── Sub1.jpg
│   └── Sub2
│       ├── Sub2.jpg
│       ├── Sub2.png
│       └── Sub2.txt
├── Dir2
│   ├── Sub1
│   │   ├── Sub1.doc
│   │   └── Sub1.txt
│   └── Sub2
│       ├── SomeTxt.txt
│       ├── Sub2.jpg
│       └── Sub2.png
├── Dir3
│   └── Dir3.txt
└── Dir4
    ├── Some.txt
    └── Sub1
        └── Dir4.txt

9 directories, 12 files

then, with the shell's globstar option enabled (shopt -s globstar)

for d in somepath/**/; do
    d="${d%/}"
    [[ -f "${d}/${d##*/}.txt" ]] && printf "%s\n" "$d"
done
somepath/Dir1/Sub2
somepath/Dir2/Sub1
somepath/Dir3

If you want to store the results in an array for later processing, you can do that using the shell's built-in mapfile (or its synonym readarray) e.g.

mapfile -t TXT_DIRS < <(
  for d in somepath/**/; do
    d="${d%/}"
    [[ -f "${d}/${d##*/}.txt" ]] && printf "%s\n" "$d"
  done
)

NOTE: it would be better practice to null-terminate the list using one of the methods discussed in bash: whitespace-safe procedural use of find into select

You can then iterate over the array like so

for d in "${TXT_DIRS[@]}"; do
    echo "$d"
done
somepath/Dir1/Sub2
somepath/Dir2/Sub1
somepath/Dir3

Alternatively, you could avoid storing the results at all and simply process each directory as you find it i.e.

for d in somepath/**/; do 
    d="${d%/}"
    [[ -f "${d}/${d##*/}.txt" ]] && echo "Doing something with $d"
done
Doing something with somepath/Dir1/Sub2
Doing something with somepath/Dir2/Sub1
Doing something with somepath/Dir3
6
  • So would I make it TXT_DIR=for d in somepath/**/; do d="${d%/}"; [[ -f "${d}/${d##*/}.txt" ]]; done to get the variable? Sep 28, 2017 at 1:28
  • You would need the command substitution syntax, $(...) i.e. TXT_DIR=$(for d in somepath/**/; do d="${d%/}"; [[ -f "${d}/${d##*/}.txt" ]] && printf "%s\n" "$d"; done). What do you intend to do with it though? Multiline text isn't often very useful - an array may be a better option. Sep 28, 2017 at 2:16
  • How would you make that as array? Eventually I want to perform an operation on the file that also needs the folder. The operation will be like this operation original/Dir1/Sub2/Sub2.txt newpath/Dir1/Sub2/Output/ Sep 28, 2017 at 2:44
  • @kingcobra1986: in Python: for path in dirs: operation(path, Path('newpath') / path.relative_to(Path('original')).parent / 'Output') where dirs is defined above
    – jfs
    Sep 28, 2017 at 14:17
  • 1
    @kingcobra1986 please see updated answer Sep 28, 2017 at 23:36
0

This is practically the same as steeldriver's answer, but it loops over each txt file instead of over each dir. Hopefully it's easier to understand too.

Since you mentioned in a comment that you don't actually need an array, I've left out that step.

shopt -s globstar
for f in ./**/*.txt; do
    d="$(dirname "$f")"
    if [[ $(basename "$f") == $(basename "$d").txt ]]; then
        printf '%s\n' "$d"
    fi
done

Output:

./Dir1/Sub2
./Dir2/Sub1
./Dir3

You must log in to answer this question.

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