For loop in bash script will not walk directories recursively

malonn

Member
Joined
May 23, 2023
Messages
61
Reaction score
31
Credits
522
The following bash scripts will not do what (I thought) they are supposed to do:
Code:
#!/bin/bash

for item in ~/*; do
    echo $item
done
Oddly enough, it prints
Code:
/root/*
I have no idea what's up with that. Some GLOB thing? Now, if I change it to
Code:
#!/bin/bash

for item in /home/*; do
    echo $item
done
Is where my thread title comes in. The latter script only enters the first directory (the username directory) and will not proceed into the $HOME directory proper and recurse as I thought it would.

Any ideas why? Every thing I Googled said that this should work.
Also, I noticed when listing the $HOME directory with
Code:
ls -a ~
there are two directories with the following names:
Code:
.
and
Code:
..
Is this in any way related?
 


The dot refers to the current working directory.
The dot pair refers to the parent directory of the current working directory.

One can check with:
Code:
ls .
ls ..
to see that the commands output the files in the respective directories.

In relation to the script, basically the for loop needs a list to read.

In the following example the user is in directory dir1, within which there is another directory named anothdir. The "ls -R" command shows all the files in both directories so that the user can check that the for loop command applied later has picked them all up, and therefore worked as intended.

Code:
[tom@min ~/lemon/file-exp/ac/dir1]$ ls -R
.:
anothdir  file1  file2  file3  file4  file5  file6  file7  file8

./anothdir:
newfile1  newfile2  newfile3  newfile4  newfile5


[tom@min ~/lemon/file-exp/ac/dir1]$ for i in $(ls *);do echo $i; done
file1
file2
file3
file4
file5
file6
file7
file8
anothdir:
newfile1
newfile2
newfile3
newfile4
newfile5

In relation to output that shows all the files under root, there are many thousands and it's a very resource intensive and time-consuming type of command to run. It could be run with a find command as root, something like:
Code:
find / -type f | wc -l
but this is not recommended. This command would get the number of ordinary files, but there are other types of files which it would likely miss like device files, pipes and sockets.
 
Last edited:
Thanks for the reply @osprey, but I am not so sure you answered my question. I'm still confused. If you read my OP, I have a for loop that iterates like so:
Code:
for item in /home/*
...
Shouldn't that line iterate through all files and folders recursively?

Also, when I use
Code:
ls -a "$HOME"
I get the expected results, except two folders: one named
Code:
.
and one named
Code:
..
What are those folders?

I understand now (thank you) that the dot is used for the current working directory, and the dot-dot character is used for one directory above the CWD as syntax when typing commands in the terminal and code in scripts. But, with my question stdout prints them as a result of a command (here: ls -a).
 
In your code "for item in /home/", the item "/home/*", is not a list provided to the for loop to read.
Try running that in a terminal running bash:
Code:
[tom@min ~/notes]$ /home/*
bash: /home/tom: Is a directory

Check the way in which the list is provided to the for loop to post #2. It could be adapted for /home by:
Code:
 $(ls /home/*)
which would provide the list, or perhaps:
Code:
$(ls /home/$USER/*)
BUT bear in mind that the user's home directory may contain many files.
 
Last edited:
Ohhh, I see now! The GLOB * only returns all items within /home. The code you provided will use ls to recursively walk directories and files. Cool deal.

I have a couple bash scripts to update. Thanks for clearing that one up, @osprey.
 
Alright. A search turned up an alternative way to recursively walk a directory
Code:
find ~ -print0 | while IFS= read -r -d '' item; do
...
done
ls named the directories it was entering (as pointed out by @osprey) like "Documents:" and then list contents. I didn't like that.
 
Alright. A search turned up an alternative way to recursively walk a directory
Code:
find ~ -print0 | while IFS= read -r -d '' item; do
...
done
ls named the directories it was entering (as pointed out by @osprey) like "Documents:" and then list contents. I didn't like that.
Post #2 using the ls command was really about proof of concept, that is, to show that the for loop needed a list, and ls was one way of doing that.

Bash scripting aficionados often advise against using the ls command in scripts, in favour of the find command which can be more precise and discriminatory in what is being sought.

I'm glad you have progressed in your quest :)
 
I have one more question that I am not able to answer. When using
Code:
find ~ -print0
find traverses the /home/ directory (as I thought it should using ~). However, when I use
Code:
find ~ -print0 | while IFS= read -r -d '' item; do
...
done
fine walks the /root/ directory. Any ideas why?
 
Works here. Trying the command in the current directory using the dot. Note the added "echo $item" in the command. If it works in this directory there's no reason to suppose it shan't work in any directory inserted in the command accordingly:
Code:
[tom@min ~/lemon]$ ls
file1  file2  file3  file4  file5  file6  file7  file8

[tom@min ~/lemon]$ find . -print0 | while IFS= read -r -d '' item;do echo $item ; done
.
./file8
./file4
./file1
./file7
./file2
./file3
./file6
./file5

The same output is available with:
Code:
[tom@min ~/lemon]$ find . -print
.
./file8
./file4
./file1
./file7
./file2
./file3
./file6
./file5
 

Staff online

Members online


Latest posts

Top