I am looking at some shell code that is meant to get the count of the number of files in a directory. It reads:
COUNT=$(ls -1 ${DIRNAME} | wc -l)
What does the -1
part mean? I can't find anything about this in any other questions, just passing references to iterating over files in a directory which isn't what I am looking at. Also, removing it from the command seems to have no effect.
答案 0 :(得分:10)
COUNT=$(ls -1 ${DIRNAME} | wc -l)
...is a buggy way to count files in a directory: ls -1
tells ls
not to put multiple files on a single line; making sure that wc -l
will then, by counting lines, count files.
Now, let's speak to "buggy":
ls
handles this is implementation-defined; some versions could double-count such files (GNU systems won't, but I wouldn't want to place bets about, say, random releases of busybox
floating around on embedded routers).${DIRNAME}
allows the directory name to be string-split and glob-expanded before being passed to ls
, so if the name contains whitespace, it can become multiple arguments. This should be "$DIRNAME"
or "${DIRNAME}"
instead....also, this is inefficient, as it invokes multiple external tools (ls
and wc
) to do something the shell can manage internally.
If you want something more robust, this version will work with all POSIX shells:
count_entries() { set -- "${1:-.}"/*; if [ -e "$1" ]; then echo "$#"; else echo 0; fi; }
count=$(count_entries "$DIRNAME") ## ideally, DIRNAME should be lower-case.
...or, if you want it to be faster-executing (not requiring a subshell), see the below (targeting only bash):
# like above, but write to a named variable, not stdout
count_entries_to_var() {
local destvar=$1
set -- "${2:-.}"/*
if [[ -e "$1" || -L "$1" ]]; then
printf -v "$destvar" %d "$#"
else
printf -v "$destvar" %d 0
fi
}
count_entries_to_var count "$DIRNAME"
...or, if you're targeting bash and don't want to bother with a function, you can use an array:
files=( "$DIRNAME"/* )
if [[ -e "${files[0]}" || -L "${files[0]}" ]]; then
echo "At least one file exists in $DIRNAME"
echo "...in fact, there are exactly ${#files[@]} files in $DIRNAME"
else
echo "No files exist in $DIRNAME"
fi
Finally -- if you want to deal with a list of file names too large to fit in memory, and you have GNU find
, consider using that:
find "$DIRNAME" -mindepth 1 -maxdepth 1 -printf '\n' | wc -l
...which avoids putting the names in the stream at all (and thus generates a stream for which one could simply measure length in bytes rather than number of lines, if one so chose).
答案 1 :(得分:2)
补充Charles Duffy's excellent answer:
他的答案没有涵盖的一个边缘案例:如果第一个目录条目恰好是损坏的符号链接,则使用-e
测试glob扩展是还不够,因为Bash总是将存在测试应用于符号链接的目标 - 在符号链接损坏的情况下,根据定义它不存在。换句话说:对于损坏的符号链接,-e
将指示 false ,即使链接本身存在。因此,完全可靠的解决方案必须使用类似[[ -e "$1" || -L "$1" ]]
的内容
(-L
测试其参数是否为符号链接,无论是否损坏。)
这里有一个略微更短的bash
替代(使用子shell):
count=$(shopt -s nullglob; entries=(*); echo "${#entries[@]}")
shopt -s nullglob
可确保模式扩展为空字符串。entries=(*)
收集数组中的所有匹配项(在当前目录中)echo "${#entries[@]}"
输出元素数组计数。getconf ARG_MAX
限制,因此应使用大型目录。请注意,以上是否计算隐藏(.*
)项也取决于dotglob
选项的状态。
但是,很容易在命令中构建固定的隐藏项包含或不包含逻辑:
明确包含隐藏的内容:
count=$(shopt -s nullglob dotglob; entries=(*); echo "${#entries[@]}")
明确排除隐藏的项目:
count=$(shopt -s nullglob; shopt -u dotglob; entries=(*); echo "${#entries[@]}")
可以将所有上述内容包含在灵活的功能中:
countEntries [<dir>] ... counts based on current state of the `dotglob` option
countEntries <dir> 0 ... counts non-hidden entries only
countEntries <dir> 1 ... counts all entries, including hidden ones
#!/usr/bin/env bash
# SYNOPSIS
# countEntries [<dir> [<includeHidden>]]
# DESCRIPTION
# <dir> defaults to .
# <includeHidden> default to the current state of `shopt dotglob`;
# a value of 0 explicitly EXcludes, 1 explicity INcludes hidden items.
countEntries() ( # Run entire function in subhell.
local dir=${1:-.} includeHidden=$2 entries
shopt -s nullglob
case $includeHidden in
0) # EXclude hidden entries
shopt -u dotglob
;;
1) # INclude hidden entries
shopt -s dotglob
;;
# Otherwise: use *current state* of `dotglob`
esac
entries=("$1"/*) # Collect in array
echo "${#entries[@]}" # Output count.
)