Μια συμβουλή που θα ακούσουν οι νέοι προγραμματιστές bash ξανά και ξανά:
Βάλε εισαγωγικά στις μεταβλητές σου!
Γιατί συμβαίνει αυτό;
Όταν επεκτείνεις μια μεταβλητή (όπως την $name), σχεδόν πάντα θέλεις να χρησιμοποιήσεις την τιμή της μεταβλητής ως μία μόνο συμβολοσειρά (ή αριθμό).
Δεν θέλεις η τιμή να αντιμετωπιστεί απροσδόκητα ως δύο ή περισσότερες ξεχωριστές συμβολοσειρές.
Τα εισαγωγικά είναι ο βασικός μηχανισμός που διασφαλίζει ότι ένα κομμάτι κειμένου αντιμετωπίζεται ως μία μόνο λέξη. Υπάρχουν δύο κύριοι τύποι εισαγωγικών.
Τα διπλά εισαγωγικά επιτρέπουν την επέκταση παραμέτρου, την αριθμητική επέκταση και την υποκατάσταση εντολής μέσα τους.
echo "This is bash version $BASH_VERSION"
Μπορείς να συμπεριλάβεις ένα κυριολεκτικό διπλό εισαγωγικό μέσα σε μια συμβολοσειρά με διπλά εισαγωγικά, βάζοντας πριν από αυτό ένα backslash.
echo "She said \"Let me teach you about bash.\""
Τα μονά εισαγωγικά δεν επιτρέπουν καμία επέκταση μέσα τους.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
Δεν είναι δυνατόν να ενσωματώσεις ένα μονό εισαγωγικό μέσα σε μια συμβολοσειρά με μονά εισαγωγικά.
Τι ακριβώς πάει στραβά με το echo $name;
Όταν το shell αναλύει μια γραμμή κώδικα, εκτελεί διάφορα είδη επεκτάσεων, με τη σειρά. Δύο από τις τελευταίες επεκτάσεις είναι ο Διαχωρισμός λέξεων και η Επέκταση ονόματος αρχείου.
Οποιαδήποτε αποτελέσματα της επέκτασης παραμέτρου, της υποκατάστασης εντολής ή της αριθμητικής επέκτασης που δεν συνέβησαν μέσα σε διπλά εισαγωγικά είναι επιλέξιμα για "διαχωρισμό λέξεων".
Το shell παίρνει το αποτέλεσμα της επέκτασης και το χωρίζει σε οποιαδήποτε ακολουθία χαρακτήρων υπάρχει στην τιμή του $IFS.
Από προεπιλογή, αυτό είναι το κενό, το tab και η νέα γραμμή.
Μπορούμε να το δούμε αυτό στην πράξη με έναν βρόχο shell:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
εμφανίζει
1 The quick brown fox jumps.
Αλλά χωρίς εισαγωγικά:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
εμφανίζει
1 The
2 quick
3 brown
4 fox
5 jumps.
Ας το δούμε με μια διαφορετική τιμή του $IFS:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
εμφανίζει
first
second
third
Μια τεχνική για να απενεργοποιήσεις τον διαχωρισμό λέξεων είναι να θέσεις το $IFS σε μια κενή συμβολοσειρά
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
εμφανίζει
1 The quick brown fox jumps.
Ωστόσο, δεν συνιστάται να αφήνεις τις μεταβλητές χωρίς εισαγωγικά, εκτός αν απενεργοποιήσεις και την επέκταση ονόματος αρχείου. Θα δούμε πώς γίνεται αυτό παρακάτω.
Τα μοτίβα "glob" χρησιμοποιούνται για να εκφράσουν ένα συνοπτικό μοτίβο που να αντιστοιχίζει ένα σύνολο αρχείων.
Για παράδειγμα, μπορείς να αντιστοιχίσεις όλα τα αρχεία Markdown σε έναν κατάλογο με *.md.
Τα μοτίβα glob υπάρχουν από την εφεύρεση του Unix.
Οι χαρακτήρες μπαλαντέρ του glob είναι:
* αντιστοιχίζει μηδέν ή περισσότερους οποιουσδήποτε χαρακτήρες,
? αντιστοιχίζει ακριβώς έναν οποιονδήποτε χαρακτήρα,
[...] αντιστοιχίζει ακριβώς έναν χαρακτήρα από το σύνολο των χαρακτήρων που παρατίθενται μέσα στις αγκύλες.
[abc] αντιστοιχίζει ακριβώς έναν χαρακτήρα που είναι είτε a είτε b είτε c.
^)
[][] είναι ένα σύνολο χαρακτήρων που θα αντιστοιχίσει είτε μια αγκύλη κλεισίματος είτε μια αγκύλη ανοίγματος.[[\]]
[x-y] αντιστοιχίζει ακριβώς έναν χαρακτήρα που βρίσκεται στο εύρος από το x έως το y
^) είτε ο τελευταίος χαρακτήρας πριν από την αγκύλη κλεισίματος.
[-xy] ή [xy-] αντιστοιχίζουν το καθένα ακριβώς έναν χαρακτήρα που είναι - ή x ή y
[x\-y]
[0-9A-Fa-f] αντιστοιχίζει ένα δεκαεξαδικό ψηφίο[^abc] αντιστοιχίζει ακριβώς έναν χαρακτήρα που ΔΕΝ είναι a ή b ή c,[^x-y] αντιστοιχίζει ακριβώς έναν χαρακτήρα που ΔΕΝ βρίσκεται στο εύρος από το x έως το y
[[:character_class:]] αντιστοιχίζει έναν χαρακτήρα που ανήκει στο ονομαζόμενο "character_class"
lower: πεζά γράμματαupper: κεφαλαία γράμματαalpha: γράμματαdigit: ψηφίαalnum: γράμματα και ψηφίαspace: κενά διαστήματαblank: οριζόντια κενά διαστήματα (κενό και tab)xdigit: δεκαεξαδικά ψηφίαword: χαρακτήρες που επιτρέπονται σε ένα αναγνωριστικό (γράμματα, αριθμοί, κάτω παύλα)punct: στίξηcntrl: χαρακτήρες ελέγχουgraph: "ορατοί" χαρακτήρεςprint: ορατοί χαρακτήρες συν το κενό[[:alpha:]] αποτελείται από [[:lower:][:upper:]]
[[:alnum:]] αποτελείται από [[:alpha:][:digit:]]
[[:word:]] αποτελείται από [[:alnum:]_]
[[:word:]] και [[:punct:]]
[[:graph:]] αποτελείται από [[:alnum:][:punct:]]
[[:print:]] αποτελείται από [[:graph:] ], δηλαδή μόνο το κενό (οκταδικό 040) και όχι οποιοδήποτε άλλο κενό διάστημα.Μπορείς να επαναλάβεις πάνω σε μια λίστα αρχείων με έναν βρόχο for
for file in *.csv; do
do_something_with "$file"
done
Πρόσεξε ότι η μεταβλητή "$file" είναι μέσα σε εισαγωγικά.
Το μοτίβο glob μπορεί να επιστρέψει αρχεία που περιέχουν κενά στο όνομά τους.
Πρέπει να βάλουμε εισαγωγικά στη μεταβλητή, ώστε η εντολή do_something_with να λάβει ακριβώς ένα όρισμα, το όνομα του αρχείου.
Τα μοτίβα glob δεν είναι κανονικές εκφράσεις.
.
?
.*
*
.* σημαίνει "αντιστοίχισε ένα όνομα αρχείου όπου ο πρώτος χαρακτήρας είναι μια κυριολεκτική τελεία ακολουθούμενη από μηδέν ή περισσότερους οποιουσδήποτε χαρακτήρες"..+
?*
Το Bash παρέχει εκτεταμένα μοτίβα που είναι πιο κοντά στις κανονικές εκφράσεις.
Υπάρχουν φορές που θέλεις να αποτρέψεις την επέκταση ονόματος αρχείου. Ο χειρισμός εισόδου του χρήστη είναι μια τέτοια περίπτωση. Δες αυτό το απόσπασμα κώδικα
read -p "Enter something: " user_input
echo $user_input
Αν ο χρήστης εισάγει έναν αστερίσκο (*), τι θα εμφανιστεί;
Η λίστα των αρχείων στον τρέχοντα κατάλογο.
Πιθανότατα αυτό δεν είναι αυτό που θέλεις να κάνεις.
Ένα άλλο παράδειγμα είναι να περάσεις μοτίβα ως ορίσματα σε προγράμματα που περιμένουν μοτίβα.
Το πρόγραμμα tr χρησιμοποιείται συχνά για χειρισμό κειμένου. Για να μετατρέψεις την τιμή μιας μεταβλητής σε πεζά, μπορείς να κάνεις το εξής
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
εμφανίζει
my puppet
Ένα συνηθισμένο λάθος είναι να ξεχάσεις να βάλεις εισαγωγικά στα ορίσματα του tr: tr [:upper:] [:lower:]
Γιατί είναι πρόβλημα αυτό;
Τα μοτίβα glob χωρίς εισαγωγικά θα επεκταθούν στη λίστα των αρχείων που αντιστοιχίζονται.
Ας υποθέσουμε ότι το άτομο που χρησιμοποιεί το script σου έχει αρχεία με ονόματα p και u και w στον τρέχοντα κατάλογο:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
Αυτό έχει ως αποτέλεσμα ένα σφάλμα!
Το μοτίβο [:upper:] χωρίς εισαγωγικά αντιστοιχίζει οποιοδήποτε από τα :, u, p, e ή r.
Το μοτίβο [:lower:] χωρίς εισαγωγικά αντιστοιχίζει οποιοδήποτε από τα :, l, o, w, e ή r.
Το Bash αντιστοιχίζει τα αρχεία p και u για το πρώτο μοτίβο, και το αρχείο w για το δεύτερο μοτίβο, και το tr καλείται έτσι
echo "$var" | tr p u w
Και αυτός είναι ο λάθος αριθμός ορισμάτων για το tr.
Μπορείς να χρησιμοποιήσεις την εντολή set για να απενεργοποιήσεις την επέκταση ονόματος αρχείου.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
Υπάρχουν αρκετές ρυθμίσεις για την ενσωματωμένη εντολή shopt που ελέγχουν πώς λειτουργεί η επέκταση ονόματος αρχείου.
Μερικές ενδιαφέρουσες είναι:
shopt -s nocaseglob: εκτέλεσε αντιστοίχιση χωρίς διάκριση πεζών/κεφαλαίωνshopt -s extglob: ενεργοποίησε τα εκτεταμένα μοτίβαshopt -s nullglob: αν δεν αντιστοιχίζεται κανένα αρχείο, αντικατέστησε το μοτίβο με τίποτα.
Η προεπιλεγμένη συμπεριφορά είναι να αφήνει το μοτίβο στη θέση του ως κυριολεκτική συμβολοσειρά.