קלט/פלט · נתונים באורך קבוע

sysseek#

למקם מחדש מטפל קובץ ברמת המערכת, תוך עקיפת buffering של PerlIO.

sysseek מזיז את ה־offset של ה־file-descriptor ברמת הקרנל של FILEHANDLE למיקום בית חדש על־ידי קריאה ל־lseek(2) הבסיסי ישירות. הוא המלווה של sysread ו־syswrite: השלושה יוצרים את משפחת I/O ללא buffering, שמדברת עם הקרנל מבלי לעבור דרך מחסנית המאגרים :perlio או דמוית stdio של Perl. יש להשתמש ב־sysseek על מטפלים שניגשים אליהם עם משפחת ה־sys; יש להשתמש ב־seek על מטפלים שניגשים אליהם עם read, readline, או print.

תקציר#

sysseek FILEHANDLE, POSITION, WHENCE
sysseek $fh, 0, 0              # rewind to start
sysseek $fh, 0, 1              # report current position
sysseek $fh, -1024, 2          # 1024 bytes before EOF

מה מקבלים בחזרה#

ה־offset של הבית המוחלט החדש בהצלחה, או undef בכישלון (כאשר $! מוגדר). מיקום של אפס מוחזר כמחרוזת dualvar "0 but true" כך שהערך מודפס כ־0 בהקשר מספרי אך נשאר אמת בהקשר בוליאני - בדיקה כמו if (sysseek ...) בטוחה ב־offset אפס.

defined(sysseek $fh, $offset, 0)
    or die "sysseek to $offset failed: $!";

מכיוון שערך ההחזרה נושא את ה־offset החדש, sysseek משמש כפול גם כפרימיטיב של ״איפה אני״ - הסיבה ש־systell המקובל הוא שורה אחת:

use Fcntl 'SEEK_CUR';
sub systell { sysseek($_[0], 0, SEEK_CUR) }

ערכי WHENCE#

WHENCE הוא מספר שלם עם שלושה ערכים בעלי משמעות. עדיף את הקבועים הסמליים מ־Fcntl - הם גם הופכים את הקריאה ניידת לפלטפורמות שאינן משתמשות ב־0 / 1 / 2:

  • 0 / SEEK_SET - POSITION נמדד מתחילת הקובץ. POSITION חייב להיות לא־שלילי.

  • 1 / SEEK_CUR - POSITION מתווסף למיקום הנוכחי. ערכים שליליים זזים אחורה, ערכים חיוביים קדימה. sysseek $fh, 0, 1 מחזיר את ה־offset הנוכחי מבלי להזיז את המצביע.

  • 2 / SEEK_END - POSITION מתווסף ל־offset של end-of-file. POSITION הוא בדרך כלל אפס או שלילי.

use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);

sysseek $fh, 0,   SEEK_SET;    # rewind
sysseek $fh, 0,   SEEK_END;    # move to EOF
sysseek $fh, -$n, SEEK_CUR;    # $n bytes back from here

בתים, לא תווים#

sysseek פועל על offsets של בתים, תמיד. אפילו כאשר למטפל יש שכבה מוכוונת־תווים כמו :encoding(UTF-8), ה־offset שהוא מקבל ומחזיר הוא ספירת בתים גולמית. זה תואם לתצוגת הקרנל אך אינו רלוונטי בפועל: sysseek נועד למטפלים שאינם משתמשים בשכבות PerlIO, וערבוב שלו עם שכבת פענוח היא אחת ממלכודות הבלבול המפורטות להלן.

אם הנתונים מוכווני־תווים, יש להשתמש ב־seek וב־tell בצד עם buffering במקום ב־sysseek.

אין לערבב עם I/O עם buffering#

sysseek מדבר ישירות אל ה־file descriptor. read, readline, print, write, seek, tell, ו־eof מדברים אל מאגר ה־PerlIO שיושב לפני ה־descriptor. מיקום המאגר ומיקום הקרנל בלתי־תלויים - sysseek מזיז את מצביע הקרנל אך אינו משטיף או מבטל את המאגר, כך שהקריאה המוצפת הבאה מחזירה בתים מיושנים מלפני ה־sysseek, והכתיבה המוצפת הבאה נוחתת איפה שהמאגר חשב שהוא היה.

הכלל פשוט: לבחור משפחה אחת לכל מטפל.

מצב גלובלי שהוא נוגע בו#

  • $! - מוגדר להודעת שגיאת המערכת בכישלון.

  • שלא כמו seek, sysseek אינו משטיף או משליך את מאגר ה־PerlIO ואינו מנקה את דגל ה־EOF על המטפל. אסימטריה זו היא מקור הסכנה של ״ערבוב״ לעיל והסיבה ששתי המשפחות אינן יכולות לחלוק מטפל.

דוגמאות#

קריאת גישה אקראית של רשומה באורך קבוע. הקובץ מחזיק רשומות בנות 128 בתים; לקפוץ ישר לרשומה 42:

use Fcntl 'SEEK_SET';
sysopen my $fh, $path, O_RDONLY or die $!;
sysseek $fh, 42 * 128, SEEK_SET or die "sysseek: $!";
sysread $fh, my $rec, 128       or die "sysread: $!";

לדווח על ה־offset הנוכחי מבלי להזיז את המצביע - המבנה systell:

use Fcntl 'SEEK_CUR';
my $pos = sysseek $fh, 0, SEEK_CUR;
defined $pos or die "sysseek: $!";
print "at byte $pos\n";

לצרף לקובץ שמוחזק פתוח לקריאה־כתיבה, ואז להחזיר מצביע כדי לנגן שוב מההתחלה:

use Fcntl qw(SEEK_SET SEEK_END);
sysseek $fh, 0, SEEK_END or die $!;
syswrite $fh, $record     or die $!;
sysseek $fh, 0, SEEK_SET or die $!;

אפס עדיין אמת - ה־dualvar מאפשר לכתוב את הצורה התמציתית מבלי להתייחס בטעות להחזרת מצביע מוצלחת ככישלון:

if (my $pos = sysseek $fh, 0, 0) {
    # entered even when $pos stringifies to "0"
    printf "rewound; pos=%d\n", $pos;
}
else {
    die "sysseek failed: $!";
}

לבצע seek מעבר ל־end-of-file כדי ליצור חור דליל, ואז לכתוב את סמן הזנב:

use Fcntl 'SEEK_SET';
sysseek $fh, 1_000_000, SEEK_SET or die $!;
syswrite $fh, "END"               or die $!;

מקרי קצה#

  • מטפלים שלא ניתן לבצע seek עליהם: pipes, סוקטים, TTYs, ורוב ההתקנים המיוחדים נכשלים כאשר $! מוגדר ל־ESPIPE (Illegal seek). ערך ההחזרה הוא undef; תמיד יש לבדוק עם defined.

  • אין לערבב עם I/O עם buffering על אותו מטפל. sysseek עוקף את מאגר ה־PerlIO; read, readline, print, write, seek, tell, ו־eof משתמשים בו. שילוב שתי המשפחות מייצר תוצאות שגויות בשקט. יש לבחור משפחה אחת לכל מטפל ולהישאר איתה.

  • אפס ממוחרזת כ־"0 but true". בדיקות בוליאניות עובדות ב־offset אפס - if (sysseek ...) נשאר אמת - אך השוואות מחרוזת מול ה־literal "0" לא:

    my $pos = sysseek $fh, 0, 0;
    $pos == 0     or die;           # true
    $pos eq "0"   and die "nope";   # false - the string is "0 but true"
    

    יש להשתמש ב־defined לזיהוי הצלחה/כישלון ובהשוואה מספרית עבור ערך ה־offset.

  • שכבות קידוד: sysseek על מטפל עם שכבת :encoding(...) היא כמעט תמיד טעות - ה־offset של הבית יכול לנחות באמצע נקודת קוד, ומשפחת ה־sys נועדה לבתים גולמיים בכל מקרה. אם הנתונים דורשים פענוח, יש להשתמש ב־seek עם המשפחה עם buffering.

  • offsets שליליים עם SEEK_SET: POSITION חייב להיות לא־שלילי כאשר WHENCE הוא 0. ערך שלילי נכשל עם EINVAL.

  • קבצים גדולים מאוד: sysseek מחזיר את ה־offset המלא של 64 ביט של הקרנל; מיקומים מעבר ל־2 GiB עוברים round-trip נכון דרך קריאות sysseek עוקבות בכל הבניות 64 ביט נתמכות.

  • מטפל קובץ סגור: מחזיר undef ומגדיר את $!; תחת use warnings נפלטת אזהרת sysseek() on closed filehandle.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • seek - מקבילה עם buffering; יש להשתמש בה על מטפלים שניגשים אליהם דרך read, readline, או print

  • sysread - הקריאה ללא buffering המשויכת ל־sysseek; שניהם מדלגים על PerlIO ומדברים ישירות עם ה־file descriptor

  • syswrite - הכתיבה ללא buffering; החבר השלישי של המשפחה ששייך לאותו מטפל כמו sysseek

  • sysopen - פותח מטפל ברמת ה־descriptor, הדרך הרגילה לקבל מטפל שתוביל אותו אחר כך עם משפחת ה־sys

  • tell - שאילתת offset נוכחי בצד עם buffering; יש לשייך עם seek, לא עם sysseek

  • Fcntl - מקור של הקבועים SEEK_SET, SEEK_CUR, SEEK_END