2016-09-10 5 views
3

端末ではls -d */と呼ぶことができます。今、私はこのように、プログラムは私のためにそれをやってみたい:第1レベルのディレクトリを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: */: No such file or directory

+4

「システム」と呼んでください。 Unix上のGlobは、シェルによって拡張されています。 'system'はあなたにシェルを与えます。 – PSkocik

+0

ありがとう@PSkocik、それでした!回答を投稿したいですか? 'system("/bin/ls -d */");'なぜ 'execv()'がこのトリックを行えなかったのかを説明してください;) – gsamaras

+0

'system()'を使うなら 'fork () 'を返します。 – unwind

答えて

3

残念ながら、シェル拡張に基づくすべてのソリューションは、最大コマンドラインの長さによって制限されます。どちらが変わるか(調べるにはtrue | xargs --show-limitsを実行してください);私のシステムでは、約2メガバイトです。はい、多くの人は、それが十分であると主張します - ビルゲイツは640キロバイトで1回でした。

(共有されていないファイルシステムで特定の並列シミュレーションを実行すると、収集フェーズで同じディレクトリに数十万のファイルがあることがあります。データを収集する最も堅牢な方法です。ごくわずかなPOSIXユーティリティが、 "Xは誰にとっても十分だ"と思うほど馬鹿だ。)

幸い、いくつかの解決法がある。一つは、代わりにfindを使用することです。(あなたは出力をソートしたい場合は、

system("/usr/bin/find . -mindepth 1 -maxdepth 1 -type d -printf '%p\n'"); 

セパレータとして\0を使用します。

system("/usr/bin/find . -mindepth 1 -maxdepth 1 -type d"); 

はまた、あなたが望むように、ロケールに依存しない、出力をフォーマットすることができますファイル名に改行を含めることができるため)-t=sortには、\0をセパレータとしても使用できます。あなたが名前をしたい場合は、配列内の

system("/usr/bin/find . -mindepth 1 -maxdepth 1 -type d -printf '%p\0' | sort -t= | tr -s '\0' '\n'"); 

代わりglob()関数を使用する:trはあなたのために改行に変換します。私が今しておきハープたいと

最後に、1は内部でこれを実装するために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()コールを上記明らか

のようなものである使用します nftw()を使用して
if (nftw(".", myfunc, NUM_FDS, FTW_ACTIONRETVAL)) { 
    /* An error occurred. */ 
} 

のみ「問題」は、ファイルのかなりの数は、関数が(NUM_FDS)を使用することができ記述子を選択することです。 POSIXによると、プロセスは常に少なくとも20個のファイル記述子を開くことができなければなりません。標準のもの(入力、出力、およびエラー)を引くと、それは17になります。しかし、上記は3以上のものを使用する可能性は低いです。

sysconf(_SC_OPEN_MAX)を使用して、プロセスが同時に使用する記述子の数を減算すると、実際の制限値を見つけることができます。現行のLinuxシステムでは、通常、プロセスあたり1024個に制限されています。

数字が4または5程度以上であれば、パフォーマンスにのみ影響します。回避方法を使用する前に、nftw()がディレクトリツリー構造にどれだけ深く入るかを判断するだけです。 、そのディレクトリ利回りbash: /bin/ls: Argument list too longエラーで

ls -d */ 

を実行している、私のシステムで

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 

:あなたはサブディレクトリのたくさんのテストディレクトリを作成したい場合は

は、以下のバッシュのようなものを使用しますfindコマンドとnftw()ベースのプログラムはすべて正常に動作します。

同じ理由でrmdir directory-*/を使用してディレクトリを削除することもできません。代わりに

find . -name 'directory-*' -type d -print0 | xargs -r0 rmdir 

を使用してください。または、ディレクトリとサブディレクトリ全体を削除するだけです。

cd .. 
rm -rf lots-of-subdirs 
+0

'find -delete'は特別な場合にはさらに簡単です。しかし、 'xargs -0'は良い例です。 GNU findの場合、 'find -exec rmdir {} +'はargsを一緒に最大サイズのグループにバッチします( 'find -exec rmdir {} \;'と違って)、xargsを置き換えることができます。 –

+1

@PeterCordes:合意。私は、 'handle = popen(" find ... -print0 "、" r ");'または 'handle = popen(" find ... -printf '%p \ n' ")を使用するかどうか疑問に思っていました。それは良いKISSのやり方であるので、特定のファイルを見つけるために 'getdelim(&name、&namesize、 '\ 0'、handle)'で実行します(ユーザーが 'find'ユーティリティまたは 'PATH')。 –

+0

編集:または 'handle = popen(" find ... -printf '\ p \ 0' ");'もちろんです。 –

3

ちょうどsystemを呼び出します。 Unix上のGlobは、シェルによって拡張されています。 systemはあなたにシェルを与えます。

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に結果を渡すことができ、それをやってのポイントはほとんどありません。

は、あなた自身をglob(3)を行うことによって、全体のフォーク-execのものを避けることができます。 :system()

+0

私はちょうど1つのディレクトリを提供するように働いたばかりで、 '*'で問題を見つけ出すことにむしろうんざりでしたが、 'globs'を 'wildcard'に置き換えることができますか? 'ls'? – usr2564301

+2

本当に低いレベルは 'fd = opendir("。 ")'と 'readdir(fd)'です。もしreaddirがディレクトリタイプを知ることができない場合は、ディレクトリに 'stat()'を使ってください。 –

+1

@RadLexus: 'ls'と他の通常のUnixプログラムは、argをワイルドカードとして扱いません。だからシェルでは、 'ls '*' 'を実行してlsにリテラル' *'を渡すことができました。 'strace ls *'を使って、args lsが実際にそれを実行するのを見てください。 DOSから移植されたプログラム(または特別な目的のためにグロブを使用するプログラム)は、glob-handlingを内蔵しているので、シェルからメタキャラクタを保護するために余分な引用符を使用する必要があります*任意のファイル名に対処したい場合は、それらも渡します。 –

2

別の少ない低レベルのアプローチ、 -

(これらの呼び出しのそれぞれは、失敗することがありますが、フォークとexecをしたいならば、あなたは適切なエラーチェックを行い、テンプレートで始める必要があります。) system()

#include <stdlib.h> 

int main(void) 
{ 
    system("/bin/ls -d */"); 
    return 0; 
} 

通知は、あなたがfork()する必要はありません。しかし、可能な限りsystem()を使用しないようにしてください。


Nomimal Animalによれば、サブディレクトリの数が大きすぎると失敗します。彼の答えをもっと見る...

+1

ディレクトリに非常に多くのサブディレクトリが含まれていると、これらのディレクトリがすべて最大コマンド行の長さを超えている場合は、この機能は動作しません。これは、シェルに依存するすべての答えに影響を与え、 'ls'のような単一のコマンドにそれらをパラメータとして提供します。詳細は私の答えを見てください。 –

+1

私に知らせるために@NominalAnimalありがとうございました。ただし、単純な使い方で使用できるので削除しません。 :) 更新しました! :) – gsamaras

3

あなたのプログラムにフォルダのリストを取得する簡単な方法を探しているならば、私はむしろスポーンレスな方法を提案し、外部プログラムを呼び出さず、標準のPOSIX opendir/readdir関数。

それはあなたのプログラムとしてほとんどと短いですが、いくつかの追加の利点があります。

  • あなたは早期廃棄システムエントリに選択することができますd_type
  • をチェックすることによって、意志のフォルダやファイルを選択してもらいます(半)隠しエントリの名前の最初の文字をテストすることによって.
  • すぐに結果を印刷するか、または後で使用できるようにメモリに保存します
  • メモリ内のリストに対して、追加する必要のない他のエントリのソートや削除など、追加の操作を行うことができます。

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/dir.h> 

int main(void) 
{ 
    DIR *dirp; 
    struct dirent *dp; 

    dirp = opendir("."); 
    while ((dp = readdir(dirp)) != NULL) 
    { 
     if (dp->d_type & DT_DIR) 
     { 
      /* exclude common system entries and (semi)hidden names */ 
      if (dp->d_name[0] != '.') 
       printf ("%s\n", dp->d_name); 
     } 
    } 
    closedir(dirp); 

    return 0; 
} 
+3

'DT_UNKNOWN'をチェックせずに' d_type'を使うのはエラーです。 'mkfs.xfs'は' -n ftype = 1'を有効にしないので、ファイルシステムは安価にファイルタイプ情報を提供しないので、d_type = DT_UNKNOWNに設定されます。 (そして、もちろんDT_UNKNOWNを持つ他のFSも)。 DT_UNKNOWNの 'stat'へのフォールバックと、シンボリックリンクのための私の答えを見てください(ディレクトリへのシンボリックリンクの場合、' */'のセマンティクスのその部分を保持します)。通常通り、より低いレベルの高性能APIは、より高いレベルのAPIより複雑さを少なくします。 –

+0

@PeterCordes:あなたの* much *より完全な答えに気づいただけです! (私はupvoteとbubblegumを噛むためにここに来たが、うーん、私はすべての票の席を外している) – usr2564301

+0

私はあなたが私の仕事を開始した後、おそらく*ちょうど私が既存の答え私が「低レベル」と呼ぶものにも近くなっていました)。私の答えはまだglibcの関数呼び出しを使う代わりにダイレクトシステムコールでアセンブリ言語になっていませんし、printfを使ったことさえあります! –

8

同じ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は、ディレクトリ以外のファイルとは異なり、正確なシステムコールインタフェースを指定しません。

これらの関数は:

// 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にディレクトリエントリと印刷ファイル情報を読み取る完全にコンパイル例もあります:

DIR *opendir(const char *name); 
struct dirent *readdir(DIR *dirp); 

は次のように使用することができます。(Linux stat(2) man pageではなく、別の例があります)。


readdir(3)のmanページには、構造体の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は、ディレクトリエントリが自身であるかどうかについては何も学ぶstatする必要がある場合にはDT_UNKNOWN、いずれかでありますディレクトリ。または、DT_DIRまたはそれ以外のものでもかまいません。その場合は、statにすることなく、ディレクトリであるかどうかを確認できます。

EXT4のようなファイルシステムと最近のXFS(新しいメタデータバージョン)は、タイプインフォメーションをディレクトリに保存するので、ディスクからiノードをロードしなくても返すことができます。これはfind -nameのための巨大なスピードアップです:それはサブディレクトリを通して再帰的に何かをstatする必要はありません。しかし、これをしないファイルシステムの場合、d_typeは、すべてのinode(ディスクからロードされていない可能性もあります)を読み込む必要があるため、常にDT_UNKNOWNになります。

時々あなたはファイル名にマッチしているだけで、タイプ情報は必要ないので、カーネルが余分なCPU時間(または特にI/O時間)を費やしていた場合は、d_type安いです。 d_typeは単なるパフォーマンスのショートカットです。あなたはいつもフォールバックが必要です(あなたが使用しているFSを知っている埋め込みシステムを書くとき、そしていつもd_typeを埋め込むことを除いて、そして将来の誰かがこれを使用しようとするときに何らかの方法で破損を検出する方法他のFSタイプのコード)

+0

Peterありがとう! – gsamaras

+1

'dirfd(3)'と 'fstatat(2)'を使うと、任意のディレクトリで作業できます。現在のものだけでなく、 – Igor

関連する問題