1

Given a root path, I am trying to loop through the sub-directories to loop through the files in each subdirectory and print the names of the files.

The directory structure is like this:

  • Root directory
    • dir2,

      • file{1..10}
    • dir3,

      • file{1..10}
    • dir4

      • file{1..10}

I want to loop through dir2 and print all the filenames in it. Then loop through dir3 and print all the file names...and so on

Here is what I have so far:

#!/bin/bash
#!/bin/sh
cd /the/root/directory
for dir in */
do
        for FILE in dir
        do
             echo "$FILE"
        done > /the/root/directory/filenames.txt
done

This is the output I get in filenames.txt:

dir

My expected output is supposed to be:

file{1..10}
file{1..10}
file{1..10}

I am a beginner to bash scripting...well scripting in general. Any help is greatly appreciated!

user1234
  • 13
  • 5
  • [Shellcheck](https://www.shellcheck.net/) identifies the broken code. – pjh Jun 14 '22 at 16:31
  • Note that ALL_UPPERCASE variable names like `FILE` are best avoided. There is a danger that they will clash with the very many special ALL_UPPERCASE variables that are used in shell programs. See [Correct Bash and shell script variable capitalization](https://stackoverflow.com/q/673055/4154375). In this case it's better to use `file` or `File`. – pjh Jun 14 '22 at 16:35
  • @pjh Thank you for your response. I thought I read somewhere that it's conventional to use all uppercase for variable names. I am glad that you pointed that out. I will make the change. – user1234 Jun 14 '22 at 16:55

4 Answers4

0

You can use the find command, and this will loop through the directories without needing the for loop

my_bash_script.sh:

find * -type d  > filenames.txt

Put this script in the same level of the directories, or you point it to the location by changing the * to the path

note: if it says permission denied in the terminal run this: chmod u+x the_script_name.sh

  • Thank you for your response! However, your suggestion just outputs the names of the directories. I need the file names in each of the directories. – user1234 Jun 14 '22 at 16:53
0

You forgot to expand $dir in your inner loop, so the loop is executing one time with FILE set to the literal string 'dir' instead of the directory name.

After that, you need a globbing pattern to expand to the filenames inside the directory.

Fixed example:

#!/bin/bash
cd /the/root/directory
for dir in */
do
        for FILE in "$dir/"*
        do
             echo "$FILE"
        done > /the/root/directory/filenames.txt
done
tjm3772
  • 2,346
  • 2
  • 10
  • I made the changes you suggested and it just outputs the filenames in dir4. It seems to skip dir2 and dir3. Any reason why it does that? Thank you for your help! – user1234 Jun 14 '22 at 16:51
  • I fixed it. I just put the redirection outside the inner loop. I used the done statement from the outer loop to redirect the output instead of the done statement from the inner loop. – user1234 Jun 14 '22 at 17:04
0

You didn't mention what your end goal is, so I'll speculate here.

If your end goal is to only see the files recursively in a list, you can run just a simple find command:

find . -type f

Or if you want to see the details:

find . -type f -ls

A nice way to view them with colors and nice-looking ansi bars is to install the tree command. Example: https://www.tecmint.com/linux-tree-command-examples/

If your needs are simple, for example, you want to do an action such as a tail -n1 on each file, you can pipe the command to xargs like this:

find . -type f | xargs tail -n1

But if your end goal is to use bash to process them in some way, then you can continue down the bash looping method as mentioned by @tjm3772.


You mentioned you were just looking for the filenames so you can just run:

find . -type f | sed 's/.*\///'

If you want to write that to a file, just redirect the output to a filename of your choice:

find . -type f | sed 's/.*\///' > filename.txt
Dean Householder
  • 549
  • 1
  • 7
  • 13
  • My end goal is to see the names of the files recursively in a list. The first command you listed (find . -type f) worked for me! Thank you! As a follow-up question, is there any way I can just list the file name and not the directory it comes from? For example, the output looks like this: './dir2/file1.txt'. I want it to look like this: 'file1.txt'. – user1234 Jun 14 '22 at 17:10
  • Sure, just add "| sed 's/.*\///'" to the end like this: find . -type f | sed 's/.*\///' – Dean Householder Jun 14 '22 at 17:39
  • I updated the original post for better formatting. – Dean Householder Jun 14 '22 at 18:18
  • It works. Thank you for your help! Would you mind explaining this part of the command: " | sed 's/.*\///' "? I understand what the sed command does but I don't understand how the backslash (\) makes this command work. Thanks in advance. – user1234 Jun 14 '22 at 19:31
  • The sed command is a Stream EDitor. It matches everything from the beginning with the .* then it looks for a / (with the \/ part) then it replaces what it found with nothing (the // part. What goes between those slashes is what would it would replace it with). This essentially replaces everything before the filename with nothing. – Dean Householder Jun 14 '22 at 21:23
0

The bash's way to do it is with the globstar expansion which expands recursively in directories

#!/usr/bin/env bash

shopt -s globstar # This enables recursively expanding files in directories

# This prints all the files in all the directories starting from /the/root/directory
printf '%s\n' /the/root/directory/**
Léa Gris
  • 17,497
  • 4
  • 32
  • 41