在Linux下,有没有一种简单的方法可以列出给定目录下的只有目录而无文件?更好地解释一下,我可以这样做:
find mydir -type d
这将得到:
mydir/src
mydir/src/main
mydir/bin
mydir/bin/classes
我希望得到的是:
mydir/src/main
mydir/bin/classes
我可以使用一个循环遍历每一行并删除前一行,如果下一行包含该路径,但我想知道是否有一种不使用bash循环的更简单的方法。
在Linux下,有没有一种简单的方法可以列出给定目录下的只有目录而无文件?更好地解释一下,我可以这样做:
find mydir -type d
这将得到:
mydir/src
mydir/src/main
mydir/bin
mydir/bin/classes
我希望得到的是:
mydir/src/main
mydir/bin/classes
我可以使用一个循环遍历每一行并删除前一行,如果下一行包含该路径,但我想知道是否有一种不使用bash循环的更简单的方法。
find . -type d | sort | awk '$0 !~ last "/" {print last} {last=$0} END {print last}'
sort
(因此如果您想要,可以将其放在最后)。 - Dennis Williamsonmydirs="$(find . ... | awk '$0 !~ ....)
")时。出于某种原因,我不断收到错误awk: (FILENAME=- FNR=1) fatal: division by zero attempted
,而且FILENAME
项有时会扩展到随机目录或位置。也许awk
在输出中返回了一个换行符或其他破坏命令替换的东西?或者连接管道进程存在问题? - user5359531如果您需要可视化的内容,tree -d
是不错的选择。
饮料 |-- 可口可乐 | |-- 樱桃味 | `-- 健怡可乐 | |-- 无咖啡因 | `-- 樱桃味 |-- 果汁 | `-- 橙汁 | `-- 家庭风味 | `-- 夸脱装 `-- 百事可乐 |-- 透明可乐 `-- 健怡可乐
brew install tree
- gatorback我想不到任何不需要循环就可以完成这个任务的方法,所以这里提供一些循环的代码:
以下代码将显示当前目录下所有叶子目录,无论它们的深度如何:
for dir in $(find -depth -type d); do [[ ! $prev =~ $dir ]] && echo "$dir" ; prev="$dir"; done
saveIFS=$IFS; IFS=$'\n'; for dir in $(find -depth -type d ); do [[ ! $prev =~ $dir ]] && echo "${dir}" ; prev="$dir"; done; IFS=$saveIFS
find -depth -type d | while read dir; do [[ ! $prev =~ $dir ]] && echo "${dir}" ; prev="$dir"; done
find .... | while read dir
而非 for dir in $(...)
,因为前者在打印任何内容之前无需执行整个查找。 - Cascabel<cmd> | while read
做不了的吗? - Cascabelfoo
和foo_bar
,那么只会输出foo_bar
。可以通过在测试中添加斜杠来解决这个问题:find -depth -type d | while read dir; do [[ ! $prev =~ $dir/ ]] && echo "${dir}" ; prev="$dir"; done
- Richard Whiteheadfind . -type d -links 2
在https://unix.stackexchange.com/questions/497185/how-to-find-only-directories-without-subdirectories中有一个适用于btrfs的解决方案,但它非常丑陋:
find . -type d \
\( -exec sh -c 'find "$1" -mindepth 1 -maxdepth 1 -type d -print0 | grep -cz "^" >/dev/null 2>&1' _ {} \; -o -print \)
有一种名为rawhide(rh)的替代方法,可以使这个过程更加容易:
rh 'd && "[ `rh -red %S | wc -l` = 0 ]".sh'
rh 'd && "[ -z \"`rh -red %S`\" ]".sh'
rh 'd && nlink == 2'
为了实现在所有文件系统上尽可能高效地工作的版本:
rh 'd && (nlink == 2 || nlink == 1 && "[ -z \"`rh -red %S`\" ]".sh)'
在普通(非btrfs)文件系统上,这将无需为每个目录添加任何额外的进程即可工作,但在btrfs上,它将需要它们。如果您有不同的文件系统混合使用,包括btrfs,则这可能是最好的选择。
Rawhide(rh)可从https://raf.org/rawhide或https://github.com/raforg/rawhide获取。它至少可以在Linux、FreeBSD、OpenBSD、NetBSD、Solaris、macOS和Cygwin上运行。
免责声明:我是rawhide的当前作者
awk
的解决方案很好、简单,但如果目录名包含在形成正则表达式模式时被认为是特殊字符,则会失败。这也会影响Bash中的~
或!=
测试。下面的方法似乎适用于BSD和GNU查找:find . -type d | sed 's:$:/:' | sort -r | while read -r dir;do [[ "${dir}" != "${prev:0:${#dir}}" ]] && echo "${dir}" && prev="${dir}”;done
find .
更改为您想要开始搜索的任何目录。sed
命令向每个由find
返回的目录添加正斜杠。sort -r
按字母顺序相反的方式对目录列表进行排序,这有利于首先列出离根最远的目录,这正是我们想要的。while read
循环逐行读取此列表,其中-r
选项进一步防止将某些字符与其他字符区分对待。!=
测试,并且中间目录的路径将短于相应叶目录的路径,因此我们的测试将当前行与截断为当前行长度的上一行进行比较。如果匹配,则可以将此行视为非叶目录进行丢弃,否则我们将打印此行并将其设置为prev
ious行以准备下一次迭代。请注意,在测试语句中需要引用字符串,否则可能会产生一些误报。find
...shopt -s nullglob globstar;printf "%s\n" **/ | sort -r | while read -r dir;do [[ "${dir}" != "${prev:0:${#dir}}" ]] && echo "${dir}" && prev="${dir}";done;shopt -u nullglob globstar
更新(2020年6月3日):这里有一个脚本,我希望它能够有所帮助。显然,你可以随意改进/调整/指出明显的问题...
#!/usr/bin/env bash
# leaf: from a given source, output only the directories
# required ('leaf folders' ) to recreate a full
# directory structure when passed to mkdir -p
usage() {
echo "Usage: ${0##*/} [-f|-g|-h|-m <target>|-s|-S|-v] <source>" 1>&2
}
# Initial variables...
dirMethod=0 # Set default method of directory listing (find -d)
addSource=0 # Set default ouput path behaviour
# Command options handling with Bash getopts builtin
while getopts ":fghm:sSv" options; do
case "${options}" in
f) # use depth-first find method of directory listing
dirMethod=0 # set again here if user sets both f and g
;;
g) # Use extended globbing and sort method of directory listing
dirMethod=1
;;
h) # Help text
echo "Leaf - generate shortest list of directories to (optionally)"
echo " fully recreate a directory structure from a given source"
echo
echo "Options"
echo "======="
usage
echo
echo "Directory listing method"
echo "------------------------"
echo "-f Use find command with depth-first search [DEFAULT]"
echo "-g Use shell globbing method"
echo
echo "Output options"
echo "--------------"
echo "-m <target> Create directory structure in <target> directory"
echo "-v Verbose output [use with -m option]"
echo "-s Output includes source directory"
echo "-S Output includes full given path of <source> directory"
echo
echo "Other options"
echo "-------------"
echo "-h This help text"
exit 0 # Exit script cleanly
;;
m) # make directories in given location
destinationRootDir="${OPTARG}"
;;
s) # Include source directory as root of output paths/tree recreation
addSource=1
;;
S) # Include full source path as root of output paths/tree recreation
addSource=2
;;
v) # Verbose output if -m option given
mdOpt="v"
;;
*) # If no options...
usage
exit 1 # Exit script with an error
;;
esac
done
shift $((OPTIND-1))
# Positional parameters handling - only one (<source>) expected
if (( $# == 1 )); then
if [[ $1 == "/" ]]; then # Test to see if <source> is the root directory /
(( dirMethod == 0 )) && sourceDir="${1}" || sourceDir=
# Set sourceDir to '/' if using find command dir generation or null if bash globbing method
else
sourceDir="${1%/}" # Strip trailing /
fi
else
usage # Show usage message and...
exit 1 # Quit with an error
fi
# Generate full pre-filtered directory list depending on requested method
if (( dirMethod == 0 )); then # find command method
dirList=$(find "${sourceDir}" -depth -type d 2>/dev/null | sed -e 's:^/::' -e '/^$/ ! s:$:/:')
# find command with depth-first search should eliminate need to sort directories
# sed -e 's:^/::' -e '/^$/ ! s:$:/:' - strip leading '/' if present and add '/'
# to all directories except root
else
shopt -s nullglob globstar dotglob
# nullglob - don't return search string if no match
# globstar - allow ** globbing to descend into subdirectories. '**/' returns directories only
# dotglob - return hidden folders (ie. those beginning with '.')
dirList=$(printf "%s\n" "${sourceDir}"/**/ | sed -e 's:^/::' | sort -r)
# sort command required so filtering works correctly
fi
# Determine directory stripping string. ie. if given path/to/source[/] as the
# source directory (src), should the output be just that of the contents of src,
# src and its contents or the path/to/src and contents?
sourceDir="${sourceDir#/}"
case "${addSource}" in
0) strip="${sourceDir}/";; # Set 'strip' to <source>
1) [[ "${sourceDir}" =~ (\/?.+)\/.+$ ]] && strip="${BASH_REMATCH[1]}/" || strip="/"
# To strip down to <source> only, check to see if matched by regex and only return matched part
# If not found, behave like -S
;;
2) strip="/";; # Set 'strip' to nothing but a forward slash
esac
# Main loop
# Feed the generated dirList into this while loop which is run line-by-line (ie. directory by directory)
while read -r dir;do
if [[ "${dir}" != "${prev:0:${#dir}}" ]]; then
# If current line is not contained within the previous line then that is a valid directory to display/create
if [[ -v destinationRootDir ]]; then # If destinationRootDir is set (-m) then create directory in <target>
mkdir -p${mdOpt} "${destinationRootDir%/}/${dir#$strip}"
# -p - create intermediate directories if they don't exist. The filtered list means no unnecessary mkdir calls
# if mdOpt is set, it is 'v', meaning mkdir will output each created directory path to stdin
# ${dir#$strip} removes the set strip value from the line before it is displayed/created
else
echo "${dir#$strip}" # Same as above but no directories created. Displayed only, so -v ignored here
fi
prev="${dir}" # Set prev to this line before the loop iterates again and the next line passed to dir
fi
done <<<"${dirList}" # This is a here string
尝试以下一行代码(在Linux和OS X上测试通过):
find . -type d -execdir sh -c 'test -z "$(find "{}" -mindepth 1 -type d)" && echo $PWD/{}' \;
这仍然是一个循环,因为它在sed
中使用了分支命令:
find -depth -type d |sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'
info sed
中的脚本(类似于uniq的工作方式)。
编辑 这是带有注释的sed
脚本(从info sed
中复制并修改):# copy the pattern space to the hold space
h
# label for branch (goto) command
:b
# on the last line ($) goto the end of
# the script (b with no label), print and exit
$b
# append the next line to the pattern space (it now contains line1\nline2
N
# if the pattern space matches line1 with the last slash and whatever comes after
# it followed by a newline followed by a copy of the part before the last slash
# in other words line2 is different from line one with the last dir removed
# see below for the regex
/^\(.*\)\/.*\n\1$/ {
# Undo the effect of
# the n command by copying the hold space back to the pattern space
g
# branch to label b (so now line2 is playing the role of line1
bb
}
# If the `N' command had added the last line, print and exit
# (if this is the last line then swap the hold space and pattern space
# and goto the end (b without a label)
$ { x; b }
# The lines are different; print the first and go
# back working on the second.
# print up to the first newline of the pattern space
P
# delete up to the first newline in the pattern space, the remainder, if any,
# will become line1, go to the top of the loop
D
这是正则表达式的作用:
/
- 开始一个模式^
- 匹配行的开头\(
- 开始捕获组(反向引用子表达式).*
- 零个或多个 (*) 任意字符 (.)\)
- 结束捕获组\/
- 斜杠 (/) (使用 \
转义).*
- 零个或多个任意字符\n
- 换行符\1
- 复制反向引用(在本例中是行开头和最后一个斜杠之间的任何内容)$
- 匹配行的结尾/
- 结束模式
find mydir -mindepth 2 -type d
可以工作,但是当你有多个最大深度时它就不会工作。你是想列出只包含其他目录的目录,还是想查看特定级别的目录结构? - Cascabel