is it safe to use ls in for loop in bash
If you want all subdirectories in /home
, use
( # run in subshell so "shopt -s dotglob" doesn't affect rest of script
shopt -s dotglob
for DIR in /home/*/; do
DIRNAME=`basename "$DIR"`
# do whatever with "$DIR" or "$DIRNAME"
echo -n "$DIRNAME"|od -t cz
done
)
Note the trailing slash and the quoted variables.
Edit:
With directories and file
mkdir /home/..abc
mkdir /home/.abc
mkdir /home/$'a\nc'
mkdir /home/'a?c'
mkdir /home/abc
mkdir /home/'foo bar'
touch /home/test
This gives me the output
0000000 . . a b c >..abc<
0000005
0000000 . a b c >.abc<
0000004
0000000 a \n c >a.c<
0000003
0000000 a ? c >a?c<
0000003
0000000 a b c >abc<
0000003
0000000 f o o b a r >foo bar<
0000007
where
ls -1 -F -b -A /home
gives me the output
..abc/
.abc/
a\nc/
a?c/
abc/
foo bar/
test
(test
is a file)
ls conditional in a linux script for loop
Because the ls
needs to be executed:
for i in $(ls ...); do
echo $i
done
Also, you might want to consider shell globbing instead:
for i in /home/svn/*; do
echo $i
done
... or find
, which allows very fine-grained selection of the properties of items to find:
for i in $(find /home/svn -type f); do
echo $i
done
Furthermore, if you can have white space in the segments of the path or the file names themselves, use a while loop (previous example adjusted):
find /home/svn -type f|while read i; do
echo $i
done
while
reads line-wise, so that the white space is preserved.
Concerning the calling of basename
you have two options:
# Call basename
echo $(basename $i)
# ... or use string substitution
echo ${i##*/}
To explain the substitution: ##
removes the longest front-anchored pattern from the string, #
up to the first pattern match, %%
the longest back-anchored pattern and %
the first back-anchored full match.
Nesting if in a for loop
Two things. Never use ls
to iterate files, and quote parameter expansions "$x"
. The for and if syntax itself is correct. I prefer to put the do
and then
on the same line though
for file in *; do
if [[ -d "$file" ]]; then
echo "$file is a directory"
elif [[ -f "$file" ]]; then
echo "$file is a regular file"
fi
done
For learning bash, I recommend reading http://mywiki.wooledge.org/BashGuide most other tutorials and guides are unfortunately not very good.
The reason for not doing for x in $(ls)
to iterate files is because for
iterates words and ls
outputs lines with filenames. if those filenames happen to contain whitespace, those filenames will be split up further into words, so you'll be iterating the words of the filenames, not the filenames. Obviously, for the simple cases that works, but why use a half-working solution when there's a shorter and more elegant way that handles all cases?
With for x in *
the shell replaces the *
with all filenames matching that pattern in the current directory (called pathname expansion), and each filename will be a separate word so it will work no matter what characters the filename contains. Filenames can contain any character (including newlines), except /
and the NUL byte (\0).
See http://mywiki.wooledge.org/ParsingLs for more on that.
As for using [[
vs [
. [
is a command inherited from the bourne shell, used to test strings, files and numbers. Bash has added a more powerful [[
keyword that can do everything [
can and more. If you're writing an sh script, you must use [
, but in bash scripts you should use the more powerful [[
and ((
syntaxes. See http://mywiki.wooledge.org/BashFAQ/031 for more about the difference between [
and [[
.
Bash Shell list files using for loop
If you're using bash 4, just use a glob:
#!/bin/bash
shopt -s globstar
for file in **/*.txt
do
if [[ ! -f "$file" ]]
then
continue
fi
echo "$file"
# Do something else.
done
Be sure to quote "$file"
or you'll have the same problem elsewhere. **
will recursively match files and directories if you have enabled globstar
.
bash command - using for i in `ls` with an if statement
for i in *; do [ $i -lt 1011 ] && rm -fr $i; done
does the job.
Note: this really removes the files and does not just print the remove commands.
Loop with KSH or BASH from ls + pattern
First, you don't want "ls" in there
I'm guessing you call your function like this
myfunc FAC*
In that case, the shell expands the pattern before invoking the function. So, you actually have something like this:
myfunc FAC1 FAC2 FAC3 ...
When you use $1, you just pick the first file and ignore the rest
In your function, use this:
function myfunc {
for my_archive in "$@"; do
echo "$my_archive"
done
}
Using the quotes exactly as I have them will protect any files with spaces in the name.
Difference between using ls and find to loop over files in a bash script
The correct way to iterate over filenames here would be
for f in strain_flame_00*.dat; do
echo "$f"
mybase=$(basename "$f" .dat)
echo "$mybase"
done
Using for
with a glob pattern, and then quoting all references to the filename is the safest way to use filenames that may have whitespace.
How to exit a bash loop using ls?
I suppose you can do:
#!/bin/bash
# ...
while (ls foo/* &> /dev/null); do myprog; sleep 1; done
If nothing matches foo/* (if no visible files are in directory foo), ls
will fail. &> /dev/null keeps ls
quiet.
Related Topics
Cmake Set Environment Variable
Setting Up Jenkins Slave on MAC Os
Bash Script to Run a Constant Number of Jobs in the Background
How to Run Command During Docker Build Which Requires a Tty
How to Determine If Pipe Can Be Written
What's the Difference Between 'Push' and 'Pushq' in At&T Assembly
How to Find Out Where Is My Code Causing Glib-Gobject-Critical
Linux Kernel Headers' Organization
The New Line Characted in the String Constant Isn't Being Recognized by Nasm
How to Get Environment Variables of Remote Host
Where Is the 'Sdk' Command Installed for Sdkman
Curl: (2) Failed Initialization
How to Get Only Filenames Without Path by Using Grep
Sudoers Nopasswd: Sudo: No Tty Present and No Askpass Program Specified