מ־sed ל־Perl#

אתם מכירים את sed. אתם חושבים במונחי s/old/new/, מתייחסים לשורות עם /pattern/ ו־$, נשענים על -n ו־p כדי להדפיס באופן בררני, ועל -i כדי לערוך קבצים במקום. מדריך זה ממפה כל אחד מההרגלים הללו אל Perl, כך שתוכלו לשמר כל רפלקס שיש לכם ולאמץ את הדברים ש־sed מקשה עליהם.

הרעיון האחד שכדאי לקחת איתכם כבר מהשורה הראשונה: Perl היא כמעט על־קבוצה של sed לעבודת טקסט. כל מה ש־sed עושה, Perl עושה - בדרך כלל עם אותו תחביר s/// - בתוספת מנוע ביטויים רגולריים אמיתי, משתנים אמיתיים, וקוד שרירותי בהחלפה. הפינות המסורבלות של sed (מרחב ההחזקה, טריקים רב־שורתיים, כל דבר הדומה לחשבון) הופכים ל־Perl רגילה שהייתם כותבים בלי לחשוב. מדריך זה פותח במיפויים חד־חד־ערכיים ואז מראה היכן Perl פשוט מסירה מגבלה של sed.

Every runnable example here was executed on pperl, a Rust reimplementation of Perl 5.42; the # ... comments show its real output. Start scripts with:

use v5.42;

שורה אחת זו מפעילה את strict, את warnings, ואת הפונקציה המובנית say (כלומר print עם תו שורה חדשה נגרר), מערך התכונות המודרני המשמש לאורך כל הדרך.

הערה

מתגי שורה־אחת ב־build הזה של pperl התאום הטבעי של sed ב־Perl הוא שורת־הקוד־האחת: perl -ne ו־perl -pe. מתגים אלה הם perl5 5.42 תקני ומוצגים לאורך מדריך זה כצורה האידיומטית. אך משפחת מתגי הלולאה המשתמעת (-n, -p, -a, -F, -i, -l) עדיין אינה מזוהה על ידי build זה של pperl - רק -e מזוהה. לכן דוגמאות שורת־הקוד־האחת שלהלן מוצגות ללא פלט, וכל אחת מהן מצומדת לצורת סקריפט־מלא עם לולאה מפורשת (while (<$fh>) { ... }) שאכן רצה על pperl היום ונושאת את הפלט המאומת. השתמשו בצורת הסקריפט המלא עד שהמתגים ייכנסו.

כיצד לקרוא את זה#

מחזור השורה#

sed מריצה את הסקריפט שלכם פעם אחת לכל שורת קלט, כשהשורה הנוכחית במרחב התבנית שלה, ומדפיסה את מרחב התבנית בסוף המחזור (אלא אם כן -n). המקבילה של Perl היא לולאה מפורשת שבה השורה ב־$_, משתנה הנושא המשתמע. המתג -p משחזר ”הדפסה אוטומטית של כל שורה“; -n משחזר ”אל תדפיס דבר אלא אם אומר לך“.

sed 's/bar/BAR/' input.txt
perl -pe 's/bar/BAR/' input.txt

הצורה הניתנת־להרצה, של סקריפט מלא, של אותה לולאה - זו שרצה על pperl היום - פותחת את הקובץ ועוברת עליו במפורש. $_ היא השורה הנוכחית, בדיוק כמו מרחב התבנית של sed:

use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
while (<$fh>) {           # each line lands in $_
    s/a/A/g;              # operate on $_ by default
    print;               # print $_ (note: keeps the newline)
}
# Apple
# bAnAnA
# cherry

(קובץ הקלט החזיק apple, banana, cherry.) המלכודת: שורה הנקראת עם <$fh> שומרת על תו השורה החדשה הנגרר שלה, בדיוק כפי שמרחב התבנית של sed עושה. print ללא ארגומנט מדפיס את $_ כולל אותו תו שורה חדשה, כך שהוא חוזר נקי הלוך־ושוב; chomp רק כשאתם מתכוונים לקצץ אותו. מספר השורה נמצא ב־$., המקבילה ב־sed למונה השורות שאתם נשענים עליו עם $= בסקריפטים של GNU sed.

החלפה: s///#

זו הכותרת הראשית: ה־s/// של sed וה־s/// של Perl הם אותו אופרטור. גם הדגלים מתיישרים - g הוא גלובלי, i הוא חסר־רגישות לאותיות גדולות/קטנות - ו־Perl מוסיפה דגלים של־sed אין מקבילה אליהם.

sed 's/colou\?r/COLOR/g' input.txt
perl -pe 's/colou?r/COLOR/g' input.txt

כברירת מחדל s/// עורך את היעד שלו במקום ומחזיר את ספירת ההחלפות. קבוצות שנלכדו הן $1, $2, … ואתם יכולים להריץ קוד שרירותי בהחלפה עם הדגל /e - דבר ש־sed אינה יכולה לעשות כלל:

use v5.42;
my $s = "items: 3 and 4";
(my $t = $s) =~ s/(\d+)/$1 * 10/ge;   # /e: replacement is Perl code
say $t;                               # items: 30 and 40

כדי לסדר מחדש לכודות (ההפניות לאחור \1, \2 של sed בהחלפה), השתמשו ב־$1, $2, וכשאתם רוצים מחרוזת חדשה במקום לערוך במקום, בדגל /r, המחזיר את התוצאה ומשאיר את המקור ללא שינוי:

use v5.42;
my $date = "2026-06-21";
my $us = $date =~ s{(\d+)-(\d+)-(\d+)}{$2/$3/$1}r;   # /r: return a copy
say $us;                                             # 06/21/2026
say $date;                                           # 2026-06-21

המלכודת: s/// חשוף משנה את היעד שלו ומחזיר ספירה, לא את המחרוזת החדשה. זו הסיבה שהדוגמאות לעיל מעתיקות תחילה ((my $t = $s) =~ s/.../.../) או משתמשות ב־/r. כתיבת my $new = ($s =~ s/a/b/) שמה את ספירת ההחלפות ב־$new, לא את המחרוזת הערוכה - הפתעה קלאסית של היום הראשון. שימו לב גם שמפרידי Perl חופשיים: s{...}{...} ו־s#...#...# מונעים הוספת לוכסן הפוך ללוכסנים בתבנית נתיב.

תעתיק: y/// הופך ל־tr///#

ה־y/abc/xyz/ של sed הוא ה־tr/abc/xyz/ של Perl. Perl שומרת על y/// ככינוי מדויק, כך ש־y/a-z/A-Z/ עובד מילולית, אך tr הוא השם הכתוב שתראו בקוד Perl.

sed 'y/abc/xyz/' input.txt
use v5.42;
my $s = "hello";
(my $t = $s) =~ tr/a-z/A-Z/;   # uppercase, like y/a-z/A-Z/
say $t;                        # HELLO

tr עושה גם דברים ש־y אינו יכול. עם החלפה ריקה הוא סופר התאמות, והמתאם s דוחס רצפים של תו - הניב הנפוץ tr -s:

use v5.42;
my $s = "hello";
my $vowels = ($s =~ tr/aeiou//);   # count, replacement empty
say $vowels;                       # 2

my $dots = "a....b...c";
(my $squeezed = $dots) =~ tr/././s; # squeeze repeats
say $squeezed;                      # a.b.c

המלכודת: tr/// אינו ביטוי רגולרי - הצד השמאלי הוא קבוצת תווים מילולית, בדיוק כמו ה־y של sed. tr/a-z// משמעו הטווח a עד z, אך tr/.*// משמעו שני התווים המילוליים . ו־*, לא ”כל מחרוזת“. לעבודת תבניות, נשענו על s///. וכמו s///, tr/// עורך במקום ומחזיר ספירה, לכן העתיקו תחילה או השתמשו בדגל /r לתעתיק לא־הרסני.

כתובות וטווחים#

לפקודת sed ניתן להקדים כתובת: מספר שורה, $ לשורה האחרונה, /pattern/, או טווח addr1,addr2. ב־Perl כתובת היא פשוט תנאי על המשפט - if בתחביר סיומת. מספר השורה הוא $.; בדיקת התבנית היא /pat/ כנגד $_; השורה האחרונה היא eof.

sed -n '2p' input.txt              # print line 2
sed -n '/banana/p' input.txt       # print matching lines
use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
while (<$fh>) {
    print if $. == 2;              # sed -n '2p'
}
# banana

טווח sed /start/,/end/ הופך לאופרטור הטווח flip-flop של Perl, /start/ .. /end/, שהוא אמת מהשורה התואמת את התבנית השמאלית עד השורה התואמת את הימנית, כולל:

use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
while (<$fh>) {
    print if /banana/ .. /cherry/;  # sed '/banana/,/cherry/p' with -n
}
# banana
# cherry

המלכודת: בבדיקה בוליאנית .. הוא אופרטור ה־flip-flop (טווח), לא אופרטור טווח־הרשימה (שהוא 1 .. 10 הבונה רשימה). Perl מבחינה ביניהם לפי הקשר - בעמדה הסקלרית/בוליאנית של if, .. הוא בורר טווח־השורות המשקף את addr1,addr2 של sed. הוא בעל מצב: הוא זוכר אם ראה את תבנית ההתחלה, כך שהוא עושה בדיוק את מה שטווח sed עושה לאורך הלולאה.

מחיקה, הדפסה, יציאה: d, p, q#

פקודות המחזור של sed בנות־האות־הבודדת ממופות למילות־מפתח של לולאת Perl. d (מחק את מרחב התבנית, דלג למחזור הבא) הוא next. q (צא) הוא last. p (הדפס) הוא print. מכיוון שהלולאה של Perl מפורשת, אלה נקראות בדיוק כמו מה שהן עושות.

sed '/banana/d' input.txt          # drop matching lines
sed '/banana/q' input.txt          # stop after the match
use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
while (<$fh>) {
    next if /banana/;              # sed '/banana/d'
    print;
}
# apple
# cherry
use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
while (<$fh>) {
    print;
    last if /banana/;              # sed '/banana/q'
}
# apple
# banana

המלכודת: תחת ההדפסה האוטומטית בסגנון -p של sed, d חייב לדלג על ההדפסה האוטומטית, מה שהוא עושה על ידי ביטול המחזור. בלולאת Perl מפורשת אתם שולטים בהדפסה בעצמכם, כך ש“מחיקה“ היא פשוט אי־הדפסת השורה - next if /pat/ לפני ה־print, או שמירה על ה־print עם unless. אין הדפסה אוטומטית נסתרת להיאבק בה; מה שאתם print הוא מה שיוצא.

מרחב ההחזקה הופך למשתנה#

זה הסעיף שבו sed מפסיקה להיות נוחה ו־Perl מפסיקה להיות קשה. ל־sed יש בדיוק חוצץ עזר אחד - מרחב ההחזקה - והיא מעבירה אליו נתונים עם h, H, g, G, x. כל דבר הזקוק לשני פריטי מצב שמורים הופך לפיתולי מרחב־החזקה. ב־Perl אתם פשוט מצהירים על משתנה. או על מערך. או על האש.

תרגיל מרחב־ההחזקה הקנוני הוא היפוך קובץ (tac), שב־sed הוא לחש אטום 1!G;h;$!d. ב־Perl זו שורה אחת מובנת מאליה:

sed -n '1!G;h;$!d' input.txt       # reverse the file (tac)
use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
my @hold;                          # the "hold space" is just an array
while (<$fh>) {
    unshift @hold, $_;             # newest line to the front
}
print @hold;
# cherry
# banana
# apple

המלכודת: אין מלכודת - זו בדיוק הנקודה. ברגע שיש לכם משתנים אמיתיים, כל קטגוריית בעיות sed הקיימת רק מפני שיש מרחב החזקה יחיד נעלמת. צריך לזכור את השורה הקודמת? my $prev. צריך סכום מתגלגל? my $sum. צריך להסיר כפילויות? האש my %seen. כשסקריפט sed נשען על h/H/x, התרגום ל־Perl הוא כמעט תמיד ”הציגו משתנה בעל שם וכתבו את הדבר המובן מאליו“.

עריכה במקום וקלט/פלט#

ה־-i של sed כותב קובץ מחדש במקום. ה־Perl האידיומטית היא אותו מתג על שורת־קוד־אחת של -p. קריאת מספר קבצים היא ברירת המחדל של sed; Perl מאייתת זאת <>, אופרטור היהלום, הקורא כל קובץ הנקוב ב־@ARGV בתורו.

sed -i 's/red/crimson/' file.txt   # edit in place
perl -i -pe 's/red/crimson/' file.txt

המקבילה הניתנת־להרצה היום היא קריאה־שינוי־כתיבה מפורשת: בלעו את השורות, המירו אותן, כתבו אותן חזרה. זה בדיוק מה ש־-i עושה מתחת למכסה המנוע, וזה רץ על pperl עכשיו:

use v5.42;
my $path = '/tmp/inplace_data.txt';   # holds: red green blue

open my $in, '<', $path or die "read: $!";
my @lines = <$in>;                    # slurp all lines (list context)
close $in;

s/e/E/g for @lines;                   # transform each line's $_

open my $out, '>', $path or die "write: $!";
print {$out} @lines;                  # write them back
close $out;
# file now holds:
# rEd
# grEEn
# bluE

לצינורות - sed באמצע שרשרת צינורות של shell - Perl פותחת מטפל קובץ של צינור ישירות: open my $fh, '-|', 'ls', '-l' קורא את פלט הפקודה, ו־open my $fh, '|-', 'sort' כותב לקלט הפקודה. המלכודת: open מחזיר שקר בכישלון במקום לזרוק חריגה, לכן הניב or die "...: $!" הוא חובה - $! היא מחרוזת שגיאת המערכת, הכישלון השקט של sed שנעשה מפורש. וקריאת קבצים מרובים עם <> מאפסת את $. לכל קובץ רק אם אתם בודקים eof במפורש; ה־$. הפשוט ממשיך לספור לאורך הזרם המשורשר.

שורות־קוד־אחת ומתגים#

שורת־הקוד־האחת היא המגרש הביתי של sed וגם של Perl. משפחת המתגים שגורמת לזה לעבוד: -e מספק את התוכנית, -n עוטף אותה בלולאה ללא הדפסה אוטומטית (ה־-n של sed), -p עוטף אותה בלולאת הדפסה אוטומטית (ברירת המחדל של sed), -i עורך במקום, -l מטפל בסיומות שורה, ו־-a/-F מפצלים אוטומטית כל שורה אל @F (טריטוריה של awk, שימושי כאן לעריכות שדה). בלוקי BEGIN { } ו־END { } רצים לפני ואחרי הלולאה, המקבילה הישירה לטריקי ההקמה־והפירוק 1i/$a של sed.

sed -n '/error/p' log.txt                  # print error lines
sed 's/[0-9]\+/N/g' input.txt              # mask numbers
perl -ne 'print if /error/' log.txt        # print error lines
perl -pe 's/\d+/N/g' input.txt             # mask numbers
perl -nE 'END { say $n } $n++ if /error/' log.txt   # count, BEGIN/END

אלה מוצגים ללא פלט מכיוון שמתגי הלולאה המשתמעת אינם מזוהים על ידי build זה של pperl עדיין (ראו את ההערה בראש). כל אחד ממופה לצורת לולאה מפורשת מאומתת: perl -ne 'CODE' הוא while (<$fh>) { CODE }, ו־perl -pe 'CODE' הוא אותו דבר עם print נגרר. הצורה הניתנת־להרצה ”ספירת שורות שגיאה“:

use v5.42;
open my $fh, '<', '/tmp/data.txt' or die "open: $!";
my $n = 0;
while (<$fh>) {
    $n++ if /banana/;          # the body of -ne
}
say $n;                        # 1

המלכודת: עד שהמתגים ייכנסו ל־pperl, כתבו את הלולאה המפורשת (היא רצה היום וזה ממילא מה שהמתגים מתרחבים אליו), ועיינו במדריך שורות־הקוד־האחת לקטלוג המתגים המלא ולעשרות מתכונים - זה הדבר בעל המנוף הגבוה ביותר לקריאה בהגעה מרקע של sed. ל־Perl יש גם תת־שגרות מלאות, הפניות, מודולים, ותכנות מונחה־עצמים אם הסקריפט שלכם גדל מעבר לשורת־קוד־אחת; ראו את מדריך התכנות מונחה־העצמים לצד הזה של השפה.

מלכודות בהגעה מ־sed#

המלכודות, מקובצות לסריקה אחרונה לפני שאתם כותבים:

  • s/// מחזיר ספירה, לא את המחרוזת החדשה. my $x = $s =~ s/a/b/ שם את ספירת ההחלפות ב־$x. העתיקו תחילה ((my $t = $s) =~ s/.../.../) או השתמשו בדגל /r כדי לקבל מחרוזת חדשה.

  • tr/// (ה־y של sed) הוא קבוצת תווים מילולית, לא ביטוי רגולרי. tr/.*// תואם את שני התווים . ו־*. לתבניות, השתמשו ב־s///.

  • .. בהקשר בוליאני הוא טווח ה־flip-flop, לא טווח־רשימה. ב־if, /a/ .. /b/ הוא בורר ה־addr1,addr2 של sed; 1 .. 10 בהקשר רשימה בונה רשימה. אותו אופרטור, מוכרע לפי הקשר.

  • <$fh> שומר על תו השורה החדשה הנגרר, כמו מרחב התבנית של sed. print מחזיר אותו הלוך־ושוב; chomp רק כשאתם מתכוונים לקצץ אותו.

  • אין הדפסה אוטומטית נסתרת. ברירת המחדל -p של sed היא דבר שאתם מאייתים בעצמכם עם print. ”מחיקת שורה“ היא פשוט אי־הדפסה שלה (next if /pat/), לא פקודה מיוחדת.

  • open אינו זורק חריגה בכישלון. השתמשו ב־open my $fh, '<', $path or die "...: $!". $! היא מחרוזת שגיאת המערכת.

  • מרחב ההחזקה איננו - השתמשו במשתנה. כל סקריפט sed הנשען על h/H/x מיתרגם למשתנה, מערך, או האש בעלי שם ב־Perl. זו פשטה, לא אובדן.

  • מתגי הלולאה המשתמעת (-n/-p/-a/-F/-i/-l) עדיין אינם מזוהים על ידי build זה של pperl. רק -e עובד היום. כתבו את הלולאה המפורשת while (<$fh>) { ... }, שהיא בדיוק מה שהמתגים מתרחבים אליו, עד שהם ייכנסו.