מחד־שורתי ל־alias של shell#
חד־שורתי הוא חד־פעמי בעיצובו. ברגע שאתם מקלידים את אותו אחד פעמיים, משהו הרוויח את מקומו בקובץ ההגדרה של ה־shell שלכם. פרק זה מכסה את המכניקה של הרמת מתכון pperl לפונקציית shell של bash או zsh, כיצד להוסיף לה פרמטרים, ומתי לעצור ולכתוב סקריפט אמיתי במקום.
בסיס מונח: יש לכם shell של bash או zsh, קובץ ~/.bashrc או ~/.zshrc (או ספריית rc.d / functions שה־shell שלכם טוען), והסבלנות לסגור ולפתוח מחדש טרמינל אחד לאחר עריכתו.
Alias מול פונקציה#
ל־bash ול־zsh יש שני מנגנונים. הם אינם בני־חליפין.
aliasהוא החלפה טקסטואלית. הוא רץ בתחילת שורת פקודה, ומחליף את שם ה־alias בערכו לפני שה־shell מנתח מחדש. ללא ארגומנטים, ללא תכונות פייפליין מעבר למה שההחלפה נותנת לכם.פונקציית shell היא חתיכת קוד shell בעלת שם. היא מקבלת ארגומנטים מיקומיים (
$1,$2,$@), קוראת מ־stdin כמו כל פקודה אחרת, ויכולה לקבל ולשלוח בפייפ אל ומפקודות אחרות.
כלל אצבע. אם המתכון אינו מקבל ארגומנטים, alias מספיק. אם הוא צריך ארגומנט כלשהו (מספר עמודה, regex, שם קובץ), השתמשו בפונקציה.
Alias - המתכון ללא ארגומנטים#
# In ~/.bashrc
alias sum-stdin='pperl -MList::Util=sum -lane '"'"'print sum @F'"'"
alias count-lines='pperl -lne '"'"'END { print $. }'"'"
המרכאות מכוערות מפני שמחרוזת בודדת־מצוטטת ב־bash אינה יכולה להכיל מרכאה בודדת; הניב '"'"' סוגר־ופותח מחדש. זוהי הסיבה העיקרית להעדיף פונקציות: הן מאפשרות לכם לצטט את ה־Perl פעם אחת, בנקיון.
פונקציה - המקרה הנפוץ#
# In ~/.bashrc
sum-stdin() { pperl -MList::Util=sum -lane 'print sum @F'; }
count-lines() { pperl -lne 'END { print $. }'; }
השימוש זהה:
$ printf '1 2 3\n4 5 6\n' | sum-stdin
6
15
פונקציות עם פרמטרים#
ה־shell משבץ את $1, $@, "$1" לתוך גוף הפונקציה לפני הרצתה. בתוך תוכנית ה־Perl, ה־$1 ברמת ה־shell מופיע כערך ליטרלי - ה־$1 של Perl עצמה (לכידת ה־regex) נשאר ללא שינוי על־ידי שיבוץ ה־shell רק אם אתם מצטטים במרכאות בודדות.
התערובת פתירה בזהירות. שני ניבים מכסים כמעט כל מקרה.
ניב 1: עמודה / ערך משובץ־shell#
השתמשו במרכאות כפולות סביב תוכנית ה־Perl כך שה־shell ירחיב את $1, ואז עשו escape ל־$ של Perl עצמה באמצעות \$.
# Sum column N of stdin: sumcol N
sumcol() {
pperl -MList::Util=sum -lane "\$s += \$F[$1]; END { print \$s }"
}
# Usage
$ cat data.tsv
10 apple 3
15 pear 7
20 plum 2
$ sumcol 2 < data.tsv
12
כל \$ הוא סימן דולר ליטרלי עבור Perl; כל $1 (ללא escape) הוא הארגומנט המיקומי הראשון שהורחב על־ידי ה־shell.
ניב 2: תוכנית במרכאות בודדות, נתונים דרך סביבה#
Perl במרכאות בודדות נשאר קריא, וערכים מגיעים לתוכנית דרך %ENV.
# Grep with a pperl regex: pgrep PATTERN [FILE...]
pgrep() {
local pat=$1
shift
pat="$pat" pperl -ne 'print if /$ENV{pat}/' "$@"
}
$ pgrep '\berror\b' app.log
זה שומר על $1 (לכידת Perl) כשמיש ונמנע ממפל ה־backslashes.
ניב 3: ערבוב שני הניבים#
# Replace column N's commas with semicolons, in place: csv-fix N FILE...
csv-fix() {
local col=$1
shift
col="$col" pperl -i.bak -F'\t' -lane '
my $c = $ENV{col};
$F[$c] =~ tr/,/;/;
print join("\t", @F);
' "$@"
}
מספר העמודה מגיע ב־$ENV{col} - ללא ריקוד escape. רשימת הקבצים מתפשטת דרך "$@" כך שרווחים בשמות קבצים שורדים.
Aliases מעשיים ששווה לשמור#
רשימה מוערה של מתכונים המרוויחים את מקומם בקובץ הגדרת shell עבור כל מי שמעבד טקסט בשורת הפקודה יומיומית.
sumcol N - סכימת עמודה N (מופרדת ברווחים)#
sumcol() {
pperl -MList::Util=sum -lane "\$s += \$F[$1]; END { print \$s }"
}
sumcolf N FS - סכימת עמודה N עם מפריד שדות מותאם אישית#
sumcolf() {
local col=$1 fs=$2
col="$col" pperl -MList::Util=sum -F"$fs" -lane '
$s += $F[$ENV{col}]; END { print $s }
'
}
$ sumcolf 3 : < /etc/passwd # sum of UIDs
colstats N - min / max / mean / count עבור עמודה N#
colstats() {
col="$1" pperl -MList::Util=qw(min max sum) -lane '
my $c = $ENV{col};
push @v, $F[$c];
END {
printf "n=%d min=%g max=%g mean=%.4f\n",
scalar @v, min(@v), max(@v), sum(@v) / @v
}
'
}
uniq-stream - ייחוד זורם, שמירה על הופעה ראשונה#
uniq-stream() { pperl -ne 'print unless $seen{$_}++'; }
בניגוד ל־sort -u, הסדר נשמר. בניגוד ל־uniq, הקלט אינו חייב להיות ממוין.
extract PAT - הדפסת כל התאמה של regex, אחת לשורה#
extract() { pat="$1" pperl -lne 'print for /$ENV{pat}/g'; }
$ extract '\b\d{3}-\d{4}\b' < phonebook.txt
555-1212
555-9876
between A B - הדפסת הכל בין regex A ל־regex B, כולל הקצוות#
between() {
a="$1" b="$2" pperl -ne 'print if /$ENV{a}/ .. /$ENV{b}/'
}
$ between 'BEGIN cert' 'END cert' < bundle.pem
json-pretty - הדפסה מודפסת־יפה של מסמך JSON יחיד#
json-pretty() {
pperl -MJSON::PP -0777 -ne '
print JSON::PP->new->pretty->canonical->encode(
JSON::PP->new->decode($_)
)
'
}
passwd-json - /etc/passwd כ־NDJSON#
passwd-json() {
pperl -MJSON::PP -F: -lane '
print JSON::PP->new->encode({
user => $F[0], uid => 0+$F[2], gid => 0+$F[3],
home => $F[5], shell => $F[-1]
})
' < /etc/passwd
}
words-by-freq - ספירת תדירויות מילים, ממוין#
words-by-freq() {
pperl -ne '
$w{lc $_}++ for /\b[[:alpha:]]+\b/g;
END { print "$w{$_} $_\n" for sort { $w{$b} <=> $w{$a} } keys %w }
'
}
$ words-by-freq < essay.txt | head -20
strip-comments LANG - הסרת הערות עבור כמה שפות נפוצות#
strip-comments() {
case "$1" in
sh|py|rb|pl) pperl -pe 's/(?<!\\)#.*//' ;;
c|cpp|rs|java|js) pperl -0777 -pe 's{//[^\n]*|/\*.*?\*/}{}gs' ;;
sql|lua) pperl -pe 's/--[^\n]*//' ;;
*) echo "unknown language: $1" >&2; return 1 ;;
esac
}
מתי לעצור ולכתוב סקריפט#
המעבר חד־שורתי → alias מסתיים כאשר:
התוכנית צריכה ענף
if/elseארוך יותר מתנאי? :יחיד.יש יותר משני או שלושה מודולי
-M.אתם מוצאים את עצמכם מנפים שגיאות באמצעות ניסוי וטעיה בשורת הפקודה.
התוכנית היא ה־alias היחיד בהגדרה שלכם שמקבל הערה מעליו המסבירה מה הוא עושה.
עמית הולך להריץ אותה.
עברו לקובץ סקריפט. שימו את ה־switches בשורת ה־shebang ותנו לתוכנית לקרוא באופן טבעי:
#!/usr/bin/env pperl
use strict;
use warnings;
use List::Util qw(sum);
my $col = shift @ARGV // die "usage: $0 COLUMN [FILE...]\n";
my $total = 0;
while (<>) {
my @f = split;
$total += $f[$col];
}
print "$total\n";
תנו לו שם הגיוני, chmod +x, והמשיכו. החד־שורתי עשה את עבודתו: הוא היה הנתיב הקצר ביותר מ־״יש לי רעיון״ ל־״אני יודע שזה עובד״; הסקריפט הוא הנתיב מ־״אני יודע שזה עובד״ ל־״עמית שלי יכול לקרוא אותו.״
לקריאה נוספת#
progression - המתכונים ש־aliases אלה עוטפים.
gotchas - מרכאות בתוך פונקציות shell ו־aliases.
numeric - מועמדים נוספים לרשימת ה־aliases, במיוחד אלה של חשבון תאריכים.