Reputation: 327
I have to count the number of executables in a directory.
I've already figured out a different way to do it (by writing to a file and then searching the file, but that is kind of ugly).
The solution that first came to me was this (the first argument is the directory path):
#!/bin/bash
noe=0
files=`ls -F $1` # because the -F option appends an indicator
# to the file, for an executable it's an '*'
# so if there is an executable 'script.sh' in the dir
# the output will be like this: 'script.sh*'
for i in $files
do
if [ `echo "$i" | grep '*$'` ] #should search for an '*' at the end...
then
let noe += 1
fi
done
echo $noe
This doesn't work because the '*' gets omitted in the for loop.
(the echo command in the for loop outputs a file-name without the '*' in the end, but works normally outside the for loop when the argument is in "")
There is a similar question about this here, and I've managed to adapt the answer to my case, but it's not explained why it can't be done with for. + I don't fully understand why is there an additional <
in the while loop
...
done < <(ls -F $1)
^
|_ I know that this means redirect to file to loop
Does the second < mean that we are redirecting the
standard input file? (this might be a stupid question)
The other question: is there a way around this with the for loop and why?
Upvotes: 2
Views: 251
Reputation: 295756
for x in $foo; do ...
...isn't looping over an array; it's looping over a string. Thus, to have the loop execute more than once, you must put the string through some kind of expansion, even if that expansion is nothing but string-splitting.
If you turned off all expansion, you would get this:
for x in "$foo"; do ...
...which will execute exactly once, making the loop useless.
Now, there's an intermediate position here: You can turn off glob expansion temporarily, but keep string splitting in place:
set -f # disable all globbing
for x in $foo; do # expand without globbing
...
done
set +f # turn globbing back on
Note that the behavior of this won't necessarily be predictable unless you control the value of the variable IFS
, which controls which characters string-splitting occurs on.
If you want to build an array, you can do that:
array=( "first element" "second element" "third element" )
for x in "${array[@]}"; do ...
...and in that case you can use for
safely.
Upvotes: 0
Reputation: 20032
Use find for finding files:
#!/bin/bash
find "$1" -type f -executable -maxdepth 1 | wc -l
Upvotes: 0
Reputation: 6171
The solution to this should not under any circumstance involve ls
.
You can iterate the files with a for-loop and use an -x
test to determine if files are executable. However, directories are usually executable too (if they're not, you cannot enter them, e.g. with cd
), so depending on whether you want to include directories in the result, you may need a -d
test too. Example:
for file in ./*; do
if [[ -x $file && ! -d $file ]]; then
printf '<%s> is an executable file that is not a directory\n' "$file"
(( count++ ))
fi
done
printf '%d executable files found\n' "$count"
As for the second question:
... done < <(ls -F $1) ^ |_ I know that this means redirect to file to loop Does the second < mean that we are redirecting the standard input file? (this might be a stupid question)
<(...)
is process substitution, and gets replaced by a filename to an fd or named pipe (depending on what the operating system supports). Any process that reads from this fd or named pipe, will get the output (stdout) of the command within <(...)
You can see this by using it with echo:
$ echo <(true) # turns into echo /dev/fd/63
/dev/fd/63
So in your case, done < <(ls...)
turns into something like done < /dev/fd/63
, which is the standard file redirection you're already familiar with.
Upvotes: 6