วิธีที่ดีที่สุดในการตรวจสอบว่าไฟล์มีอยู่ใน C คืออะไร?


436

มีวิธีที่ดีกว่าเพียงแค่พยายามเปิดไฟล์หรือไม่

int exists(const char *fname)
{
    FILE *file;
    if ((file = fopen(fname, "r")))
    {
        fclose(file);
        return 1;
    }
    return 0;
}

ฉันคิดว่าฉันจะให้คำตอบกับวิธีการเข้าถึงแม้ว่าวิธีการทางสถิติจะเป็นทางเลือกที่สมเหตุสมผล แต่การเข้าถึงทำให้งานสำเร็จได้
Dave Marshall เมื่อ

1
คุณจริงๆเพียงแค่ต้องการที่จะตรวจสอบสำหรับการดำรงอยู่? หรือคุณต้องการตรวจสอบและเขียนลงในไฟล์หากยังไม่มีอยู่ ถ้าเป็นเช่นนั้นดูคำตอบของฉันด้านล่างสำหรับรุ่นที่ไม่ได้รับผลกระทบจากสภาพการแข่งขัน
Dan Lenski

6
ฉันไม่เห็น - เกิดอะไรขึ้นกับวิธี fopen / fclose
Johannes Schaub - litb

16
@ JohannesSchaub-litb: สิ่งหนึ่งที่ผิดกับfopen()/ fclose()วิธีคือคุณอาจไม่สามารถเปิดไฟล์เพื่ออ่านแม้ว่ามันจะมีอยู่ ตัวอย่างเช่น/dev/kmemมีอยู่ แต่กระบวนการส่วนใหญ่ไม่สามารถเปิดได้แม้กระทั่งการอ่าน /etc/shadowเป็นอีกหนึ่งไฟล์ดังกล่าว แน่นอนว่าทั้งสองstat()และaccess()พึ่งพาความสามารถในการเข้าถึงไดเรกทอรีที่มีไฟล์; การเดิมพันทั้งหมดจะปิดหากคุณไม่สามารถทำได้ (ไม่ได้รับอนุญาตให้ดำเนินการในไดเรกทอรีที่มีไฟล์)
Jonathan Leffler

1
if (file = fopen(fname, "r"))จะให้คำเตือน ใช้วงเล็บล้อมรอบคำสั่งภายในคำสั่ง ifif ((file = fopen(fname, "r")))
Joakim

คำตอบ:


595

เงยหน้าขึ้นมองฟังก์ชั่นที่พบในaccess() unistd.hคุณสามารถแทนที่ฟังก์ชั่นของคุณด้วย

if( access( fname, F_OK ) != -1 ) {
    // file exists
} else {
    // file doesn't exist
}

นอกจากนี้คุณยังสามารถใช้R_OK, W_OKและX_OKในสถานที่ของF_OKการตรวจสอบสิทธิ์ในการอ่าน, เขียนได้รับอนุญาตและผู้ได้รับอนุญาต (ตามลำดับ) มากกว่าการดำรงอยู่และคุณสามารถหรือใด ๆ ของพวกเขาร่วมกัน (เช่นตรวจสอบทั้งการอ่านและเขียนได้รับอนุญาตใช้R_OK|W_OK)

อัปเดต : โปรดทราบว่าใน Windows คุณไม่สามารถใช้W_OKในการทดสอบการอนุญาตการเขียนได้อย่างน่าเชื่อถือเนื่องจากฟังก์ชั่นการเข้าถึงไม่ได้คำนึงถึง DACL access( fname, W_OK )อาจส่งคืน 0 (สำเร็จ) เนื่องจากไฟล์ไม่มีชุดคุณลักษณะอ่านอย่างเดียว แต่คุณยังอาจไม่ได้รับอนุญาตให้เขียนไฟล์


67
POSIX เป็นมาตรฐาน ISO มันกำหนดการเข้าถึง () C เป็นอีกหนึ่งมาตรฐาน ISO มันไม่ใช่.
Jonathan Leffler

16
มีข้อผิดพลาดที่เกี่ยวข้องกับการเข้าถึง () มีหน้าต่าง TOCTOU (เวลาของการตรวจสอบ, เวลาในการใช้งาน) ของช่องโหว่ระหว่างการใช้ access () และสิ่งอื่น ๆ ที่คุณทำหลังจากนั้น [... เพื่อดำเนินการต่อ ... ]
Jonathan Leffler

23
[... ต่อเนื่อง ... ] ค่อนข้างลึกลับในระบบ POSIX การเข้าถึง () ตรวจสอบว่า UID จริงและ GID จริงแทนที่จะเป็น UID ที่มีประสิทธิภาพและ GID ที่มีประสิทธิภาพ สิ่งนี้สำคัญสำหรับโปรแกรม setuid หรือ setgid เท่านั้น แต่ก็มีความสำคัญอย่างมากเนื่องจากอาจให้คำตอบที่ 'ผิด'
Jonathan Leffler

3
ฉันวิ่งข้ามคำถามนี้เมื่อมองหาเหตุผลที่access()แตกในรหัสของฉัน ฉันย้ายจาก DevC ++ เป็น CodeBlocks และหยุดทำงาน ดังนั้นจึงไม่ผิดพลาด +1 เพิ่มเติมกับ @Leffler
Ben

11
ส่วนใหญ่ใช่ (ตกลงเพื่อใช้access()ตรวจสอบการมีอยู่ของไฟล์) แต่ในโปรแกรม SUID หรือ SGID แม้อาจไม่ถูกต้อง หากไฟล์ทดสอบอยู่ในไดเรกทอรีที่ UID จริงหรือ GID จริงไม่สามารถเข้าถึงได้access()อาจรายงานว่าไม่มีไฟล์ดังกล่าวเมื่อมีอยู่ ลึกลับและเป็นไปไม่ได้? ใช่.
Jonathan Leffler

116

ใช้statแบบนี้:

#include <sys/stat.h>   // stat
#include <stdbool.h>    // bool type

bool file_exists (char *filename) {
  struct stat   buffer;   
  return (stat (filename, &buffer) == 0);
}

และเรียกมันว่าสิ่งนี้:

#include <stdio.h>      // printf

int main(int ac, char **av) {
    if (ac != 2)
        return 1;

    if (file_exists(av[1]))
        printf("%s exists\n", av[1]);
    else
        printf("%s does not exist\n", av[1]);

    return 0;
}

4
@ LudvigANorin: ในระบบดังกล่าวมีโอกาสที่จะaccess()มีปัญหาและมีตัวเลือกที่จะใช้ในการสร้างaccess()และstat()ทำงานกับไฟล์ขนาดใหญ่ (ใหญ่กว่า 2 GB)
Jonathan Leffler

14
คุณอาจจะชี้ไปที่เอกสารเกี่ยวกับความล้มเหลวหลังจาก 2 GB หรือไม่ นอกจากนี้ทางเลือกในกรณีดังกล่าวคืออะไร?
chamakits

@JonathanLeffler ไม่statต้องทนทุกข์ทรมานจากช่องโหว่ TOCTOU เดียวกับaccess? (ไม่ชัดเจนสำหรับฉันว่ามันจะดีกว่า)
Telemachus

9
ทั้งสองstat()และaccess()ประสบช่องโหว่ TOCTOU (เช่นนั้นlstat()แต่fstat()มีความปลอดภัย) ขึ้นอยู่กับว่าคุณจะทำอะไรโดยขึ้นอยู่กับว่ามีหรือไม่มีไฟล์ การใช้ตัวเลือกที่ถูกต้องopen()เป็นวิธีที่ดีที่สุดในการจัดการกับปัญหา แต่อาจเป็นเรื่องยากในการกำหนดตัวเลือกที่เหมาะสม ดูเพิ่มเติมการอภิปรายเกี่ยวกับ EAFP (ง่ายต่อการขอการให้อภัยกว่าอนุญาต) และ LBYL (Look ก่อน Leap) - ดูLBYL VS EAFP ในชวายกตัวอย่างเช่น
Jonathan Leffler

87

โดยปกติเมื่อคุณต้องการตรวจสอบว่ามีไฟล์อยู่หรือไม่นั่นเป็นเพราะคุณต้องการสร้างไฟล์นั้นหากไม่มี คำตอบของ Graeme Perrowนั้นดีถ้าคุณไม่ต้องการสร้างไฟล์นั้น แต่ก็มีความเสี่ยงที่จะเกิดสภาวะแย่งชิงหากคุณ: กระบวนการอื่นสามารถสร้างไฟล์ระหว่างที่คุณตรวจสอบว่ามีอยู่หรือไม่และคุณเปิดมันเพื่อเขียนไฟล์ . (อย่าหัวเราะ ... นี่อาจมีผลกระทบด้านความปลอดภัยไม่ดีหากไฟล์ที่สร้างเป็น symlink!)

หากคุณต้องการตรวจสอบการมีอยู่ของไฟล์และสร้างไฟล์หากไฟล์นั้นไม่มีอยู่แบบอะตอมมิกเพื่อให้ไม่มีเงื่อนไขการแข่งขันให้ใช้สิ่งนี้:

#include <fcntl.h>
#include <errno.h>

fd = open(pathname, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) {
  /* failure */
  if (errno == EEXIST) {
    /* the file already existed */
    ...
  }
} else {
  /* now you can use the file */
}

8
หากคุณกำลังจะใช้ O_CREAT คุณจะต้องระบุโหมด (สิทธิ์) เป็นอาร์กิวเมนต์ที่สามเพื่อเปิด () พิจารณาว่าควรใช้ O_TRUNC หรือ O_EXCL หรือ O_APPEND หรือไม่
Jonathan Leffler

6
Jonathan Leffler ถูกต้องตัวอย่างนี้ต้องใช้ O_EXCL เพื่อทำงานตามที่เขียนไว้
แรนดี้พรอคเตอร์

6
นอกจากนี้คุณต้องระบุโหมดเป็นอาร์กิวเมนต์ที่สาม: เปิด (ล็อก O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR)
andrew cooke

4
ควรสังเกตว่าสิ่งนี้มีความปลอดภัยเท่ากับระบบไฟล์ที่เป็นไปตาม POSIX โดยเฉพาะอย่างยิ่งเวอร์ชันเก่าของ NFS มีสภาพการแข่งขันสูงมาก O_EXCL ควรหลีกเลี่ยง! มีวิธีแก้ไขปัญหามีเอกสารในopen(2)(บน Linux หน้า man ของระบบปฏิบัติการของคุณอาจแตกต่างกัน) แต่มันค่อนข้างน่าเกลียดและอาจไม่สามารถต้านทานต่อผู้โจมตีที่เป็นอันตรายได้
Kevin

โปรดทราบว่าในการใช้สิ่งนี้ด้วยFILE*คุณต้องใช้วิธี posix fdopen(fd,"flags")เพื่อสร้างFILE*
Gem Taylor

32

ใช่. stat()ใช้ stat(2)ดูหน้าคนสำหรับ

stat()จะล้มเหลวหากไฟล์ไม่มีอยู่ไม่เช่นนั้นอาจสำเร็จได้ หากมีอยู่ แต่คุณไม่มีสิทธิ์เข้าถึงแบบอ่านไปยังไดเร็กทอรีที่มีอยู่มันจะล้มเหลว แต่ในกรณีนั้นวิธีการใด ๆ จะล้มเหลว (คุณจะตรวจสอบเนื้อหาของไดเรกทอรีที่คุณไม่เห็นตามสิทธิ์การเข้าถึงได้อย่างไร เพียง แต่คุณทำไม่ได้)

คุณสามารถใช้งานaccess()ได้เช่นกัน อย่างไรก็ตามฉันชอบstat()ราวกับว่าไฟล์นั้นมีอยู่มันจะทำให้ฉันได้รับข้อมูลที่เป็นประโยชน์มากมายทันที (เมื่อมันถูกปรับปรุงครั้งล่าสุดมันมีขนาดใหญ่เท่าใดเจ้าของและ / หรือกลุ่มที่เป็นเจ้าของไฟล์สิทธิ์การเข้าถึงและอื่น ๆ )


5
การเข้าถึงจะได้รับการแจ้งล่วงหน้าหากคุณต้องการทราบว่ามีไฟล์อยู่หรือไม่เท่านั้น สถิติ () อาจมีขนาดใหญ่ได้ยินถ้าคุณไม่ต้องการข้อมูลเพิ่มเติมทั้งหมด
Martin Beckett

4
จริงๆแล้วเมื่อฉันแสดงรายการไดเรกทอรีโดยใช้คำสั่ง ls มันเรียก stat สำหรับทุกไฟล์ที่มีอยู่และที่ทำงาน ls มีค่าใช้จ่ายที่มีขนาดใหญ่ค่อนข้างใหม่สำหรับฉัน จริงๆแล้วคุณสามารถเรียกใช้ ls ในไดเรกทอรีที่มีไฟล์หลายพันไฟล์และส่งคืนได้ในเสี้ยววินาที
Mecki

2
@Mecki: stat มีค่าใช้จ่ายที่ไม่ใช่ศูนย์เพิ่มเติมเมื่อเปรียบเทียบกับการเข้าถึงระบบที่รองรับการเชื่อมโยง นี่เป็นเพราะการเข้าถึงจะต้องดูรายการไดเรกทอรีในขณะที่ stat ต้องค้นหาไอโหนดเช่นกัน บนอุปกรณ์จัดเก็บข้อมูลที่มีเวลาในการค้นหาที่ไม่ถูกต้อง (เช่นเทป) ความแตกต่างอาจมีความสำคัญเนื่องจากรายการไดเรกทอรีและไอโหนดไม่น่าจะอยู่ติดกัน
Kevin

3
@Kevin ถ้าคุณไม่ผ่าน F_OK เท่านั้นให้access()ตรวจสอบสิทธิ์การเข้าถึงไฟล์ของไฟล์และสิ่งเหล่านี้จะถูกเก็บไว้ใน inode สำหรับไฟล์นั้นและไม่ได้อยู่ในรายการไดเรกทอรี (อย่างน้อยสำหรับระบบไฟล์ทั้งหมดที่มีโครงสร้างเหมือน inode) . ดังนั้นจึงaccess()ต้องเข้าถึงไอโหนดในลักษณะเดียวกับที่stat()ต้องเข้าถึง ดังนั้นสิ่งที่คุณพูดจะถือเป็นจริงถ้าคุณไม่ตรวจสอบสิทธิ์ใด ๆ ! และที่จริงแล้วในบางระบบaccess()ก็มีการใช้งานบนstat()(เช่น glibc บน GNU Hurd ทำอย่างนั้น) ดังนั้นจึงไม่มีการรับประกันในตอนแรก
Mecki

@Mecki: ใครพูดอะไรเกี่ยวกับการตรวจสอบสิทธิ์? ฉันพูดถึง F_OK โดยเฉพาะ และใช่บางระบบมีการใช้งานไม่ดี การเข้าถึงจะเร็วอย่างน้อยเท่ากับสถิติในทุกกรณีและอาจเร็วขึ้นในบางเวลา
เควิน

9
FILE *file;
    if((file = fopen("sample.txt","r"))!=NULL)
        {
            // file exists
            fclose(file);
        }
    else
        {
            //File not found, no memory leak since 'file' == NULL
            //fclose(file) would cause an error
        }

1
สิ่งนี้จะทำให้หน่วยความจำรั่วหรือไม่? คุณไม่เคยปิดไฟล์หากมีอยู่
LegionMammal978

1
นี่เป็นวิธีที่ดีและเรียบง่าย หากคุณอยู่ใน Windows MSVC ให้ใช้สิ่งนี้แทน: (fopen_s(file, "sample.txt", "r"))เนื่องจากfopen()ถูกพิจารณาว่าเลิกใช้แล้ว (หรือปิดใช้งานข้อผิดพลาดที่เลิกใช้แล้ว แต่ไม่แนะนำให้ทำ)
Nikos

15
fopen()เป็นมาตรฐาน C มันจะไม่ไปไหน เป็นเพียง "เลิกใช้งาน" โดย Microsoft เท่านั้น อย่าใช้fopen_s()จนกว่าคุณจะต้องการรหัสเฉพาะแพลตฟอร์มที่ไม่สามารถพกพาได้
Andrew Henle

เรียก fclose () เพื่ออะไร? จำเป็นต้องกำหนดตัวแปร 'ไฟล์' ก่อน!
Jenix

1
ตัวแปร 'file' ที่นี่มีค่าขยะ ทำไมต้องปิดมันในตอนแรก? คุณเพิ่งโทร 'fclose (SOME_RANDOM_ADDRESS);' ..
Jenix

6

จากความช่วยเหลือ Visual C ++ ฉันมักจะไปด้วย

/* ACCESS.C: This example uses _access to check the
 * file named "ACCESS.C" to see if it exists and if
 * writing is allowed.
 */

#include  <io.h>
#include  <stdio.h>
#include  <stdlib.h>

void main( void )
{
   /* Check for existence */
   if( (_access( "ACCESS.C", 0 )) != -1 )
   {
      printf( "File ACCESS.C exists\n" );
      /* Check for write permission */
      if( (_access( "ACCESS.C", 2 )) != -1 )
         printf( "File ACCESS.C has write permission\n" );
   }
}

นอกจากนี้ยังมีค่าโหมดการสังเกตของ:_access(const char *path,int mode)

  • 00: การดำรงอยู่เท่านั้น

  • 02: การอนุญาตให้เขียน

  • 04: อ่านสิทธิ์

  • 06: อ่านและเขียนสิทธิ์

เนื่องจากคุณfopenอาจล้มเหลวในสถานการณ์ที่มีไฟล์อยู่ แต่ไม่สามารถเปิดได้ตามที่ร้องขอ

แก้ไข: เพียงอ่านโพสต์ของ Mecki stat()ดูเหมือนเป็นวิธีที่จะไป โฮครวญ


การเข้าถึงจะได้รับการแจ้งล่วงหน้าหากคุณต้องการทราบว่ามีไฟล์อยู่หรือไม่เท่านั้น สถิติ () อาจมีขนาดใหญ่ได้ยิน
Martin Beckett


3

ฉันคิดว่าฟังก์ชั่นaccess ()ซึ่งพบได้unistd.hเป็นตัวเลือกที่ดีสำหรับLinux(คุณสามารถใช้สถิติได้ด้วย)

คุณสามารถใช้มันได้เช่นนี้:

#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>

void fileCheck(const char *fileName);

int main (void) {
    char *fileName = "/etc/sudoers";

    fileCheck(fileName);
    return 0;
}

void fileCheck(const char *fileName){

    if(!access(fileName, F_OK )){
        printf("The File %s\t was Found\n",fileName);
    }else{
        printf("The File %s\t not Found\n",fileName);
    }

    if(!access(fileName, R_OK )){
        printf("The File %s\t can be read\n",fileName);
    }else{
        printf("The File %s\t cannot be read\n",fileName);
    }

    if(!access( fileName, W_OK )){
        printf("The File %s\t it can be Edited\n",fileName);
    }else{
        printf("The File %s\t it cannot be Edited\n",fileName);
    }

    if(!access( fileName, X_OK )){
        printf("The File %s\t is an Executable\n",fileName);
    }else{
        printf("The File %s\t is not an Executable\n",fileName);
    }
}

และคุณจะได้รับผลลัพธ์ต่อไปนี้:

The File /etc/sudoers    was Found
The File /etc/sudoers    cannot be read
The File /etc/sudoers    it cannot be Edited
The File /etc/sudoers    is not an Executable
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.