# מ־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: ```perl use v5.42; ``` שורה אחת זו מפעילה את `strict`, את `warnings`, ואת הפונקציה המובנית `say` (כלומר `print` עם תו שורה חדשה נגרר), מערך התכונות המודרני המשמש לאורך כל הדרך. #### NOTE מתגי שורה־אחת ב־build הזה של pperl התאום הטבעי של sed ב־Perl הוא שורת־הקוד־האחת: `perl -ne` ו־`perl -pe`. מתגים אלה הם **perl5 5.42 תקני** ומוצגים לאורך מדריך זה כצורה האידיומטית. אך משפחת מתגי הלולאה המשתמעת (`-n`, `-p`, `-a`, `-F`, `-i`, `-l`) **עדיין אינה מזוהה על ידי build זה של `pperl`** - רק `-e` מזוהה. לכן דוגמאות שורת־הקוד־האחת שלהלן מוצגות *ללא* פלט, וכל אחת מהן מצומדת לצורת סקריפט־מלא עם לולאה מפורשת (`while (<$fh>) { ... }`) ש**אכן** רצה על `pperl` היום ונושאת את הפלט המאומת. השתמשו בצורת הסקריפט המלא עד שהמתגים ייכנסו. ## כיצד לקרוא את זה - [מחזור השורה]() - הלולאה המשתמעת של sed הופכת ל־`while (<$fh>)` ו־`$_`. - [החלפה: `s///`]() - אותו אופרטור, עם ביטויים רגולריים אמיתיים. - [תעתיק: `y///` הופך ל־`tr///`]() - הפקודה `y`, ששמה שונה. - [כתובות וטווחים]() - מספרי שורות, תבניות, ו־`/a/ .. /b/`. - [מחיקה, הדפסה, יציאה: `d`, `p`, `q`]() - `next`, `print`, `last`. - [מרחב ההחזקה הופך למשתנה]() - היכן ש־sed קשה ו־Perl טריוויאלית. - [עריכה במקום וקלט/פלט]() - `-i`, קריאת קבצים, צינורות. - [שורות־קוד־אחת ומתגים]() - `-n`, `-p`, `-e`, `BEGIN`/`END`. - [מלכודות בהגעה מ־sed]() - סרקו לפני שאתם כותבים. ## מחזור השורה sed מריצה את הסקריפט שלכם פעם אחת לכל שורת קלט, כשהשורה הנוכחית במרחב התבנית שלה, ומדפיסה את מרחב התבנית בסוף המחזור (אלא אם כן `-n`). המקבילה של Perl היא לולאה מפורשת שבה השורה ב־`$_`, משתנה הנושא המשתמע. המתג `-p` משחזר ”הדפסה אוטומטית של כל שורה“; `-n` משחזר ”אל תדפיס דבר אלא אם אומר לך“. ```sed sed 's/bar/BAR/' input.txt ``` ```perl perl -pe 's/bar/BAR/' input.txt ``` הצורה הניתנת־להרצה, של סקריפט מלא, של אותה לולאה - זו שרצה על `pperl` היום - פותחת את הקובץ ועוברת עליו במפורש. `$_` היא השורה הנוכחית, בדיוק כמו מרחב התבנית של sed: ```perl 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 sed 's/colou\?r/COLOR/g' input.txt ``` ```perl perl -pe 's/colou?r/COLOR/g' input.txt ``` כברירת מחדל `s///` עורך את היעד שלו במקום ומחזיר את ספירת ההחלפות. קבוצות שנלכדו הן `$1`, `$2`, … ואתם יכולים להריץ **קוד שרירותי** בהחלפה עם הדגל `/e` - דבר ש־sed אינה יכולה לעשות כלל: ```perl 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`, המחזיר את התוצאה ומשאיר את המקור ללא שינוי: ```perl 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 sed 'y/abc/xyz/' input.txt ``` ```perl 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`: ```perl 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 sed -n '2p' input.txt # print line 2 sed -n '/banana/p' input.txt # print matching lines ``` ```perl 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/`, שהוא אמת מהשורה התואמת את התבנית השמאלית עד השורה התואמת את הימנית, כולל: ```perl 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 sed '/banana/d' input.txt # drop matching lines sed '/banana/q' input.txt # stop after the match ``` ```perl use v5.42; open my $fh, '<', '/tmp/data.txt' or die "open: $!"; while (<$fh>) { next if /banana/; # sed '/banana/d' print; } # apple # cherry ``` ```perl 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 sed -n '1!G;h;$!d' input.txt # reverse the file (tac) ``` ```perl 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 sed -i 's/red/crimson/' file.txt # edit in place ``` ```perl perl -i -pe 's/red/crimson/' file.txt ``` המקבילה הניתנת־להרצה היום היא קריאה־שינוי־כתיבה מפורשת: בלעו את השורות, המירו אותן, כתבו אותן חזרה. זה בדיוק מה ש־`-i` עושה מתחת למכסה המנוע, וזה רץ על `pperl` עכשיו: ```perl 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 sed -n '/error/p' log.txt # print error lines sed 's/[0-9]\+/N/g' input.txt # mask numbers ``` ```perl 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` נגרר. הצורה הניתנת־להרצה ”ספירת שורות שגיאה“: ```perl 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`, כתבו את הלולאה המפורשת (היא רצה היום וזה ממילא מה שהמתגים מתרחבים אליו), ועיינו ב[מדריך שורות־הקוד־האחת](../oneliner/index.md) לקטלוג המתגים המלא ולעשרות מתכונים - זה הדבר בעל המנוף הגבוה ביותר לקריאה בהגעה מרקע של sed. ל־Perl יש גם תת־שגרות מלאות, הפניות, מודולים, ותכנות מונחה־עצמים אם הסקריפט שלכם גדל מעבר לשורת־קוד־אחת; ראו את [מדריך התכנות מונחה־העצמים](../oop/index.md) לצד הזה של השפה. ## מלכודות בהגעה מ־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>) { ... }`, שהיא בדיוק מה שהמתגים מתרחבים אליו, עד שהם ייכנסו.