在终端中,我可以拨打ls -d */
。现在我想要一个c程序为我做这个,就像这样:
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main( void )
{
int status;
char *args[] = { "/bin/ls", "-l", NULL };
if ( fork() == 0 )
execv( args[0], args );
else
wait( &status );
return 0;
}
这将ls -l
一切。但是,当我在尝试时:
char *args[] = { "/bin/ls", "-d", "*/", NULL };
我将收到运行时错误:
ls:* /:没有这样的文件或目录
答案 0 :(得分:10)
执行此操作的最低级别方法是使用相同的Linux系统调用ls
。
请查看strace -efile,getdents ls
的输出:
execve("/bin/ls", ["ls"], [/* 72 vars */]) = 0
...
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents(3, /* 23 entries */, 32768) = 840
getdents(3, /* 0 entries */, 32768) = 0
...
getdents是一个特定于Linux的系统调用。该手册页说明它是由libc's readdir(3)
POSIX API function引用的。
最低级可移植方式(可移植到POSIX系统),是使用libc函数打开目录并读取条目。 POSIX未指定确切的系统调用接口,与非目录文件不同。
这些功能:
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
可以像这样使用:
// print all directories, and symlinks to directories, in the CWD.
// like sh -c 'ls -1UF -d */' (single-column output, no sorting, append a / to dir names)
// tested and works on Linux, with / without working d_type
#define _GNU_SOURCE // includes _BSD_SOURCE for DT_UNKNOWN etc.
#include <dirent.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
DIR *dirhandle = opendir("."); // POSIX doesn't require this to be a plain file descriptor. Linux uses open(".", O_DIRECTORY); to implement this
//^Todo: error check
struct dirent *de;
while(de = readdir(dirhandle)) { // NULL means end of directory
_Bool is_dir;
#ifdef _DIRENT_HAVE_D_TYPE
if (de->d_type != DT_UNKNOWN && de->d_type != DT_LNK) {
// don't have to stat if we have d_type info, unless it's a symlink (since we stat, not lstat)
is_dir = (de->d_type == DT_DIR);
} else
#endif
{ // the only method if d_type isn't available,
// otherwise this is a fallback for FSes where the kernel leaves it DT_UNKNOWN.
struct stat stbuf;
// stat follows symlinks, lstat doesn't.
stat(de->d_name, &stbuf); // TODO: error check
is_dir = S_ISDIR(stbuf.st_mode);
}
if (is_dir) {
printf("%s/\n", de->d_name);
}
}
}
还有一个完全可编辑的例子,可以在Linux stat(3posix)
man page中读取目录条目和打印文件信息。(不是Linux stat(2)
man page;它有一个不同的例子)。
readdir(3)
的手册页说struct dirent的Linux声明是:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all filesystem types */
char d_name[256]; /* filename */
};
d_type是DT_UNKNOWN
,在这种情况下,您需要stat
来了解目录条目本身是否为目录。或者它可以是DT_DIR
或其他内容,在这种情况下,您可以确定它是否是一个目录,而不必stat
。
我认为某些文件系统(如EXT4)和最新的XFS(使用新的元数据版本)会在目录中保留类型信息,因此无需从磁盘加载inode即可返回。这对于find -name
来说是一个巨大的加速:它不需要通过子目标来统计任何东西。但对于不执行此操作的文件系统,d_type
将始终为DT_UNKNOWN
,因为填写它需要读取所有inode(甚至可能无法从磁盘加载)。
有时你只是匹配文件名,并且不需要类型信息,所以如果内核花费了大量额外的CPU时间(或特别是I / O时间)来填充d_type
,那就不好了什么时候不便宜d_type
只是一种表现捷径;你总是需要一个后备(除了写一个嵌入式系统,你知道你正在使用什么FS并且它总是填充d_type
,并且你有一些方法来检测未来有人的破损尝试在另一个FS类型上使用此代码。)
答案 1 :(得分:4)
只需致电system
即可。 Unix上的Globs由shell扩展。 system
会给你一个shell。
你可以通过自己glob(3)来避免整个fork-exec的事情:
int ec;
glob_t gbuf;
if(0==(ec=glob("*/", 0, NULL, &gbuf))){
char **p = gbuf.gl_pathv;
if(p){
while(*p)
printf("%s\n", *p++);
}
}else{
/*handle glob error*/
}
您可以将结果传递给衍生出来的ls
,但这样做几乎没有意义。
(如果你想做fork和exec,你应该从一个执行正确错误检查的模板开始 - 每个调用都可能失败。)
答案 2 :(得分:4)
如果您正在寻找一种简单的方法来获取程序中的文件夹列表,我宁愿建议使用spawnless方式,而不是调用外部程序,并使用标准POSIX opendir
/ {{1函数。
几乎与您的程序一样短,但还有其他一些优势:
readdir
d_type
.
答案 3 :(得分:4)
不幸的是,所有基于shell扩展的解决方案都受到最大命令行长度的限制。其中有所不同(运行true | xargs --show-limits
以查明);在我的系统上,它大约是2兆字节。是的,许多人认为这就足够了 - 比尔盖茨就像640千字节一样。
(在非共享文件系统上运行某些并行模拟时,在收集阶段,我偶尔会在同一目录中拥有数万个文件。是的,我可以这样做,但这恰好是最简单的收集数据的最有力的方法。很少有POSIX实用程序实际上足以假设&#34; X对每个人都足够了。#);
幸运的是,有几种解决方案。一种是使用find
代替:
system("/usr/bin/find . -mindepth 1 -maxdepth 1 -type d");
您也可以根据需要格式化输出:
system("/usr/bin/find . -mindepth 1 -maxdepth 1 -type d -printf '%p\n'");
如果要对输出进行排序,请使用\0
作为分隔符(因为允许文件名包含换行符),-t=
sort
使用\0
作为分隔符分隔符也是。 tr
会将它们转换为换行符:
system("/usr/bin/find . -mindepth 1 -maxdepth 1 -type d -printf '%p\0' | sort -t= | tr -s '\0' '\n'");
如果您想要数组中的名称,请改用glob()
函数。
最后,因为我喜欢不时地竖琴,所以可以使用POSIX nftw()
函数在内部实现:
#define _GNU_SOURCE
#include <stdio.h>
#include <ftw.h>
#define NUM_FDS 17
int myfunc(const char *path,
const struct stat *fileinfo,
int typeflag,
struct FTW *ftwinfo)
{
const char *file = path + ftwinfo->base;
const int depth = ftwinfo->level;
/* We are only interested in first-level directories.
Note that depth==0 is the directory itself specified as a parameter.
*/
if (depth != 1 || (typeflag != FTW_D && typeflag != FTW_DNR))
return 0;
/* Don't list names starting with a . */
if (file[0] != '.')
printf("%s/\n", path);
/* Do not recurse. */
return FTW_SKIP_SUBTREE;
}
和使用上述内容的nftw()
调用显然类似于
if (nftw(".", myfunc, NUM_FDS, FTW_ACTIONRETVAL)) {
/* An error occurred. */
}
使用nftw()
时唯一的&#34;问题&#34; 是选择函数可能使用的大量文件描述符(NUM_FDS
)。 POSIX表示进程必须始终能够拥有至少20个打开的文件描述符。如果我们减去标准值(输入,输出和错误),则会留下17.但上述情况不太可能超过3个。
您可以使用sysconf(_SC_OPEN_MAX)
找到实际限制,并减去您的流程可能同时使用的描述符数量。在当前的Linux系统中,每个进程通常限制为1024个。
好处是,只要该数字至少为4或5左右,它只会影响性能:它只是确定nftw()
在目录树结构中的深度,然后才能使用变通办法。
如果要创建包含大量子目录的测试目录,请使用以下Bash:
mkdir lots-of-subdirs
cd lots-of-subdirs
for ((i=0; i<100000; i++)); do mkdir directory-$i-has-a-long-name-since-command-line-length-is-limited ; done
在我的系统上,运行
ls -d */
在该目录中产生bash: /bin/ls: Argument list too long
错误,而find
命令和基于nftw()
的程序运行正常。
出于同样的原因,您也无法使用rmdir directory-*/
删除目录。使用
find . -name 'directory-*' -type d -print0 | xargs -r0 rmdir
代替。或者只删除整个目录和子目录,
cd ..
rm -rf lots-of-subdirs
答案 4 :(得分:2)
另一种不太低级的方法,system():
#include <stdlib.h>
int main(void)
{
system("/bin/ls -d */");
return 0;
}
注意system()
,您不需要fork()
。但是,我记得我们应该尽可能避免使用system()
!
正如Nomimal Animal所说,当子目录的数量太大时,这将失败!看到他的答案更多......