Παγίδες και εκπλήξεις#
Οι τρόποι με τους οποίους τα μονογραμμικά του pperl κάνουν σιωπηρά κάτι διαφορετικό από αυτό που εννοούσε ο χρήστης του κελύφους. Κάθε ενότητα ονοματίζει μια παγίδα, δείχνει το σύμπτωμα της και δίνει τη διόρθωση. Διαβάστε το συνεχόμενα μία φορά για να εμπεδώσετε τα μοτίβα· επιστρέψτε όταν μια συνταγή συμπεριφέρεται περίεργα.
Η κόλαση των εισαγωγικών#
Η μεγαλύτερη μεμονωμένη πηγή χαμένου χρόνου σε δουλειά με μονογραμμικά.
Τα μονά εισαγωγικά είναι ο φίλος σας#
Στα bash και zsh, το κείμενο μέσα σε μονά εισαγωγικά δεν παρεμβάλλεται. Καμία επέκταση $VAR, καμία εκτέλεση με ανάστροφους χαρακτήρες, καμία αντικατάσταση ιστορικού ! (το zsh δεν το κάνει· το bash το κάνει, και δαγκώνει). Χρησιμοποιείτε μονά εισαγωγικά για προγράμματα Perl εκτός αν έχετε λόγο να μην το κάνετε.
# Correct
pperl -ne 'print if /$var/' file # Perl sees literal $var
# Trap
pperl -ne "print if /$var/" file # shell expands $var before pperl sees it
Η έκπληξη ! σε διαδραστικό bash#
$ pperl -E 'say "hello!"'
bash: !": event not found
Η επέκταση ιστορικού του bash πυροδοτείται σε ! χωρίς εισαγωγικά (και σε μερικές μορφές με διαφυγή). Λύσεις:
Το
set +Hστο~/.bashrcαπενεργοποιεί την επέκταση ιστορικού.Διαφύγετε το
!όταν γράφετε την εντολή:"hello\!".Διατηρήστε τη συμβολοσειρά μονών εισαγωγικών του κελύφους γύρω από το πρόγραμμα - το
!είναι αδρανές μέσα σε'...'εφόσον η επέκταση ιστορικού είναι απενεργοποιημένη.
Ένα μονό εισαγωγικό μέσα σε συμβολοσειρά μονών εισαγωγικών#
Το bash δεν έχει διαφυγή γι” αυτό. Η λύση είναι ο χορός κλεισίματος-ανοίγματος τεσσάρων χαρακτήρων: το '\'' διαβάζεται ως κλείσιμο-εισαγωγικού, κυριολεκτικό-εισαγωγικό, άνοιγμα-εισαγωγικού.
# Want Perl to see: print "it's here"
pperl -e 'print "it'\''s here\n"'
Για οποιοδήποτε πρόγραμμα με περισσότερες από μία ενσωματωμένες αποστρόφους, εγκαταλείψτε το '...' και χρησιμοποιήστε q/.../ μέσα σε διπλά εισαγωγικά:
pperl -e "print q/it's here/, qq/\n/"
ή μεταφέρετε το πρόγραμμα σε αρχείο.
Ο καταρράκτης διαφυγών $ υπό διπλά εισαγωγικά#
Αν εισάγετε το πρόγραμμα Perl σε διπλά εισαγωγικά (επειδή θέλετε το $1 να παρεμβληθεί από το κέλυφος), οι ίδιες οι μεταβλητές της Perl χρειάζονται \$:
col=3
pperl -lane "\$s += \$F[$col]; END { print \$s }" # works
Αυτό είναι ευανάγνωστο μία φορά. Μετά το τρίτο τέτοιο μονόγραμμο στη σειρά, μεταφερθείτε σε μεταβλητές περιβάλλοντος:
col=3
col=$col pperl -lane '$s += $F[$ENV{col}]; END { print $s }'
Locale και κωδικοποίηση#
Το προεπιλεγμένο I/O του pperl είναι bytes. Αν η είσοδός σας είναι UTF-8, η Perl εξακολουθεί να βλέπει συμβολοσειρά bytes μέχρι να ζητήσετε διαφορετικά.
Το σύμπτωμα#
$ echo 'héllo' | pperl -lne 'print length'
6 # "h" + 2 bytes for é + 3 more = 6
$ echo 'héllo' | pperl -CSD -lne 'print length'
5 # correct
Η διόρθωση είναι το -C (δείτε switches):
-CSD- STDIN/STDOUT/STDERR και ανοίγματα αρχείων όλα UTF-8.-CS- μόνο stdio.-CA- αντιμετωπίζει το@ARGVως UTF-8.
Αντιστοίχιση regex σε UTF-8#
Χωρίς -C, το \w ταιριάζει μόνο με χαρακτήρες λέξης ASCII. Με -C, το \w ταιριάζει με γράμματα/ψηφία Unicode όπως ορίζονται από τους πίνακες ιδιοτήτων Unicode της Perl.
# Count "word" characters in a multilingual file
pperl -CSD -lne '$c += () = /\w/g; END { print $c }' file.txt
Mojibake στην έξοδο#
Αν το pperl διαβάζει UTF-8 καθαρά αλλά το τερματικό δείχνει αλλοιωμένους χαρακτήρες, το πρόβλημα είναι παρακάτω - πιθανώς το LC_ALL ή η ίδια η κωδικοποίηση του τερματικού. Τα locale και tput το διαγιγνώσκουν· αυτό δεν είναι πρόβλημα του pperl.
Επιτόπια επεξεργασία με -i#
Πάντα .bak μέχρι την επαλήθευση#
# Destructive if the pattern is wrong
pperl -i -pe 's/OLD/NEW/g' *.conf
# Safe
pperl -i.bak -pe 's/OLD/NEW/g' *.conf
# ...verify...
rm *.conf.bak
Ένα λάθος μοτίβο με σκέτο -i αντικαθιστά το περιεχόμενο του αρχείου με κάτι άχρηστο. Δεν υπάρχει ανάκτηση πλην του ελέγχου εκδόσεων. Χρησιμοποιήστε -i.bak, επιβεβαιώστε, διαγράψτε.
Η παγίδα του κενού αρχείου#
Το -i γράφει ό,τι εκπέμπει το -p (ή το -n συν ρητές εκτυπώσεις). Αν το πρόγραμμα δεν τυπώσει τίποτα, το αρχείο γίνεται κενό.
# Wrong: -i with -n and no print - empties the file
pperl -i -ne '/KEEP/ && print' file.txt # prints only lines matching KEEP
# Same thing, probably what you meant: keep the matching lines
pperl -i -ne 'print if /KEEP/' file.txt
Και τα δύο είναι σωστά. Το θέμα: υπό -n, σας ανήκει κάθε byte της εξόδου.
Επιτόπια επεξεργασία σε πολλά αρχεία με προειδοποιήσεις ιδιοκτησίας#
Το -i δημιουργεί ένα νέο αρχείο και το μετονομάζει. Στο Linux, αυτό σημαίνει ότι το νέο αρχείο κληρονομεί την ιδιοκτησία και τη μάσκα umask του καλούντος χρήστη. Ρυθμίσεις ιδιοκτησίας root που επεξεργάζονται από μη-root χρήστες θα αποτύχουν· ρυθμίσεις ιδιοκτησίας root που επεξεργάζονται από root θα διατηρήσουν το mode αλλά μπορεί να χάσουν το πλαίσιο SELinux. Γνωρίστε το μοντέλο απειλών σας πριν στρέψετε το -i στο /etc.
Ο τελεστής «διαμάντι» <> και το μαγικό άνοιγμα#
Το <> (το «διαμάντι») είναι από όπου διαβάζουν τα -n / -p. Διαβάζει από κάθε όρισμα στο @ARGV ως όνομα αρχείου, καταφεύγοντας στο STDIN αν το @ARGV είναι κενό.
Το «-» είναι το STDIN#
Αν ένα όρισμα είναι "-", το <> διαβάζει από το STDIN εκείνη τη στιγμή. Χρήσιμο για διαπλοκή:
echo prefix | pperl -ne 'print' - trailer.txt
# prints: prefix, then contents of trailer.txt
Μετα-χαρακτήρες στα ονόματα αρχείων (και γιατί έχουν λιγότερη σημασία από ό,τι είχαν)#
Ιστορικά, το σκέτο <> αντιμετώπιζε το ">foo" ως «άνοιξε το foo για εγγραφή» - ένα άνοιγμα δύο ορισμάτων που διάβαζε τη λειτουργία από το όνομα αρχείου. Στη σύγχρονη Perl, το <> χρησιμοποιεί εσωτερικά open τριών ορισμάτων, οπότε ονόματα αρχείων που αρχίζουν με <, >, | ή περιέχουν σωληνώσεις αντιμετωπίζονται ως απλά ονόματα αρχείων. Καμία επιφάνεια επίθεσης στα ονόματα αρχείων εισόδου με το σύγχρονο pperl.
Το όνομα αρχείου βρίσκεται στο $ARGV#
pperl -lne 'print "$ARGV: $_" if /\bERROR\b/' *.log
Το $ARGV ενημερώνεται καθώς ανοίγει κάθε αρχείο. Το "-" είναι η ειδική τιμή για το STDIN.
Το $. συνεχίζει να μετρά μεταξύ αρχείων#
Το $. δεν μηδενίζεται όταν το <> περνά στο επόμενο αρχείο. Αν θέλετε αριθμούς γραμμής ανά αρχείο:
pperl -lne 'close ARGV if eof; print "$ARGV:$.: $_" if /TODO/' *.pl
Το close ARGV if eof μηδενίζει το $. όταν τελειώνει το τρέχον αρχείο.
Παγίδες διαχωριστή εγγραφών#
Το -l αφαιρεί και έπειτα αποκαθιστά - μία φορά#
Το -l εφαρμόζει chomp στην είσοδο και ορίζει το $\ στον διαχωριστή που έφαγε το chomp για την έξοδο. Εντάξει υπό -n / -p. Αλλά το -l εφαρμοσμένο δύο φορές (ας πούμε, προσθέσατε -l μετά από -0) μπερδεύει τον κόσμο:
# Read NUL-delimited input, output newline-terminated
pperl -0 -lpe '' # correct: -l sets $\ = "\n"
# But if you write -l0 Perl parses that as -l with argument 0:
pperl -l0 -pe '' # $\ becomes NUL - probably NOT what you wanted
Βάλτε το -l μετά το -0 (χωρίς όρισμα στο -l) όταν θέλετε το τυπικό ζεύγος.
Η λειτουργία παραγράφου διατηρεί τα τελικά κενά#
Το -00 (λειτουργία παραγράφου) ορίζει $/ = "". Η Perl διαβάζει μέχρι και συμπεριλαμβανομένου του διαχωριστή κενής γραμμής. Χωρίς -l, ο διαχωριστής μένει προσαρτημένος σε κάθε παράγραφο· τυπώνοντάς τον πάλι διατηρείται η κενή γραμμή. Με -l, ο διαχωριστής υφίσταται chomp και το $\ = "\n" προσαρτά έναν χαρακτήρα αλλαγής γραμμής στην έξοδο - οι παράγραφοι συμπτύσσονται.
$ printf 'a\n\nb\n\nc\n' | pperl -00 -pe ''
a
b
c
$ printf 'a\n\nb\n\nc\n' | pperl -00 -lpe ''
a
b
c
Ξέρετε ποιο από τα δύο θέλετε.
Το -0777 και το $.#
Υπό -0777, κάθε αρχείο είναι μία εγγραφή. Το $. αυξάνεται ανά εγγραφή, οπότε το $. είναι ο αριθμός των αρχείων που έχουν επεξεργαστεί, όχι ο αριθμός των γραμμών.
pperl -0777 -e 'while (<>) { print "file $.: length=", length, "\n" }' *.txt
# file 1: length=1024
# file 2: length=2048
Η ερμηνεία του ορίσματος του -F#
Το -F δέχεται regex. Εκδόσεις της Perl πριν την 5.10 απαιτούσαν να γράψετε τους οριοθέτες· το σύγχρονο pperl δέχεται και τις δύο μορφές:
pperl -F: -lane '...' # bare: Perl quotes it
pperl -F/:/ -lane '...' # explicit delimiters
pperl -F'\t' -lane '...' # tab - regex form
Το συνηθισμένο λάθος είναι ένας κυριολεκτικός στηλοθέτης σε κέλυφος όπου το τερματικό τον κατάπιε:
pperl -F' ' -lane '...' # fragile: tab may be a space in your paste
Write -F'\t' unless you are pasting from known-good source.
Προτεραιότητα τελεστών γύρω από το print#
Το print έχει χαμηλότερη προτεραιότητα από τους περισσότερους τελεστές. Αυτό σκοντάφτει όσους έχουν λάθος αντανακλαστικό από awk για την Perl:
# Wrong: prints into a filehandle named "..."
pperl -ne 'print "out.txt" $_'
# Right: parentheses or commas
pperl -ne 'print $_, "\n"'
pperl -ne 'print("foo\n")'
# Wrong: the binary || applies to print's argument list
pperl -ne 'print if $_ || "never"' # works but obscure
# One particular case worth memorising:
pperl -ne 'print (1+1)*2' # prints 2, not 4
# ^ print(1+1) is the function call; *2 is applied to its return value
Όταν αμφιβάλλετε, βάλτε παρενθέσεις γύρω από τα ορίσματα του print ή τερματίστε το πρόγραμμα με ρητό ερωτηματικό.
Το tr δεν είναι regex#
Τα tr / y δέχονται σύνολα χαρακτήρων, όχι μοτίβα. Το tr/\d/X/ ΔΕΝ ταιριάζει με ψηφία - αντικαθιστά κυριολεκτικούς χαρακτήρες ανάστροφης-καθέτου-d.
pperl -pe 'tr/0-9/X/' # replace digits with X (character range)
pperl -pe 's/\d/X/g' # replace digits with X (regex)
Χρησιμοποιήστε tr για δουλειές κλάσης χαρακτήρων (μετατροπή πεζών-κεφαλαίων, διαγραφή, εύρη) και s/// όταν χρειάζεστε οποιαδήποτε μηχανική regex.
Εμβέλεια BEGIN και END#
Τα μπλοκ BEGIN { ... } και END { ... } είναι μέρος του προγράμματος, όχι του σώματος του βρόχου. Μεταβλητές που δηλώνονται με my μέσα στο BEGIN δεν είναι ορατές στο σώμα του βρόχου. Χρησιμοποιήστε our ή καθολικές μεταβλητές για κατάσταση που πρέπει να επιμείνει από το BEGIN στον βρόχο:
# Wrong: $greeting is not in scope inside the loop
pperl -ne 'BEGIN { my $greeting = "Hi"; } print "$greeting $_"'
# Right: declare without my, or use our
pperl -ne 'BEGIN { $greeting = "Hi"; } print "$greeting $_"'
Στην πράξη, τα περισσότερα μπλοκ BEGIN κάνουν αρχικοποίηση με παρενέργειες ($\ = "\n", ορισμό μορφής) αντί για συνδέσεις μεταβλητών.
Το die και η σύμβαση \n#
Το die χωρίς τελικό \n προσαρτά " at -e line N.\n" στο μήνυμά του. Με τελικό \n, τυπώνει το μήνυμα ως έχει. Επιλέξτε σκόπιμα:
# Developer diagnostic - want the location
pperl -E 'die "unreachable" if $x > 100'
# User-facing message - don't leak program structure
pperl -E 'die "bad input\n" unless /\d+/'
Ο ίδιος κανόνας ισχύει για το warn.
Λείπει χαρακτήρας αλλαγής γραμμής στο τέλος του αρχείου#
Αν η τελευταία γραμμή ενός αρχείου εισόδου δεν έχει τελικό χαρακτήρα αλλαγής γραμμής, το -l εξακολουθεί να εφαρμόζει chomp σε ό,τι υπάρχει εκεί (τίποτα), και το $_ εξακολουθεί να στερείται \n. Το -p το ξανατυπώνει χωρίς τελικό χαρακτήρα αλλαγής γραμμής, και η επόμενη γραμμή εξόδου του κελύφους τρέχει πάνω της. Σπάνια πρόβλημα ορθότητας· συχνά πρόβλημα εμφάνισης.
pperl -pe '' no-final-newline.txt # output lacks trailing \n