Bash: Κωδικοί εξόδου και έλεγχος ροής

dimitris | Πέμ, 08/29/2013 - 07:34 | 18' | 17

Σε αυτό το άρθρο θα δούμε τις δομές ελέγχου, επαναλήψεων, διακλαδώσεων και συναρτήσεων, που είναι απαραίτητες όταν γράφετε περίπλοκα scripts σε Bash ή όταν το πρόγραμμα σας εξαρτάται από το αποτέλεσμα άλλων προγραμμάτων. Αν τα παρακάτω σας φαίνονται κινέζικα καλύτερα να διαβάσετε πρώτα μια εισαγωγή στο Bash.

Πως μπορεί, λοιπόν, το script σας να ξέρει εάν συμβαίνει κάτι, δηλαδή εάν είναι, όπως λέμε, αληθές; Η απάντηση βρίσκεται στους κωδικούς εξόδου ή επιστροφής (return codes) που αφήνουν ή “επιστρέφουν” τα προηγούμενα scripts ή εντολές που τρέχετε.

Κάθε πρόγραμμα που εκτελείται από τη γραμμή εντολών επιστρέφει έναν κωδικό εξόδου όταν τερματίζεται. Ο κωδικός 0 σημαίνει ότι η εκτέλεση ήταν επιτυχής, ενώ τα λάθη σηματοδοτούνται με τιμές από το 1 έως το 255. Το 126, λόγου χάρη, σημαίνει ότι υπήρχε ένα πρόβλημα στις άδειες του αρχείου.

Όταν η τελευταία εντολή σε ένα script είναι μια εντολή εξόδου (exit) χωρίς παραμέτρους, τότε ο κωδικός εξόδου όλου του script θα είναι ο κωδικός εξόδου της γραμμής πριν από την exit. Η ειδική μεταβλητή $? περιέχει πάντα τον κωδικό εξόδου της τελευταίας εντολής ενός script ή της γραμμής εντολών. Για να το επιβεβαιώσετε δώστε τις εξής εντολές:

exit 50
echo $?

Τελεστές αγκύλης

Το κέλυφος σας επιτρέπει να ελέγξετε εξωτερικά αντικείμενα ή γεγονότα είτε με την εσωτερική εντολή test είτε με τους τελεστές αγκύλης [ ]. Η τελευταία είναι μια πιο κομψή λύση. Ορίστε μερικά παραδείγματα:

[ -f mailrc ] # έλεγχος αρχείου. Προσοχή στα κενά!

[ -d MailDir ] # έλεγχος καταλόγου

[ $COUNTER ] # έλεγχος αλήθειας μεταβλητής

[ “$ΝΑΜΕ” -eq “Jim” ] # έλεγχος τιμής

Όπως βλέπετε αυτοί οι έλεγχοι γίνονται σε αρχεία, αριθμούς ή συμβολοσειρές (strings). Οι πρώτες δύο γραμμές επιστρέφουν True αν το mailrc ειναι αρχείο και ο MailDir είναι πράγματι υποκατάλογος. Ο τρίτος έλεγχος θα επιστρέφει False μέχρι η $COUNTER να γίνει ίση με 1, ενώ ο τελευταίος έλεγχος θα επιστρέψει True μόνο αν η $NAME είναι ίση με Jim. Για περισσότερα δείτε την σελίδα man του bash. Θυμηθείτε ότι και οι δομές (( .. )) και let επιστρέφουν κωδικό εξόδου 0 αν οι υπολογισμοί τους οδηγούν σε μη μηδενική τιμή.

Βρόχοι επανάληψης

Αφού καταλάβαμε πως λειτουργούν οι έλεγχοι και οι κωδικοί εξόδου σε ένα script ας κάνουμε κάτι χρήσιμο. Ένα script συνήθως έχει μια γραμμική ροή: κάνε αυτό, μετά κάνε εκείνο και μετά το άλλο ... Πιο πολύπλοκα scripts επαναλαμβάνουν μερικά βήματα αρκετές φορές ή να κάνουν κάποιες επιλογές ενώ τρέχουν. Ας δούμε ποιες δομές του bash δίνουν στα scripts σας τέτοιου είδους αυτονομία. Ένας βρόχος for απλώς κάνει κάτι σε ή με κάθε στοιχείο μιας δεδομένης λίστας:

for XYZ in list_of_arguments
  do
    #κάτι με χρήση της τρέχουσας τιμής της XYZ
  done

Μπορείτε επίσης να χρησιμοποιήσετε τους βρόχους for από τη γραμμή των εντολών αρκεί να βάζετε ένα ελληνικό ερωτηματικό “ανάμεσα” σε κάθε γραμμή, π.χ.:

for XYZ in "Hello" "Jim"; do echo $XYZ; done

Μπορείτε να δουλέψετε με arrays δύο διαστάσεων με ένα επαναληπτικό βρόχο: περνώντας από κάθε στοιχείο του array μία φορά για την πρώτη διάσταση του και μία για τη δεύτερη. Μία πιο ευανάγνωστη εναλλακτική που υπάρχει είναι η εντολή set ακολουθούμενη από δύο παύλες, που αναθέτει κάθε sub-string της μιας μεταβλητής στις μεταβλητές $1, $2, κτλ:

for member in “Giannis Athina” “Anna Patra” “Basilis Thessaloniki”
 do
  set -- $member
  echo "$1 lives in $2" 
done

Αυτό θα σας χρησιμεύσει όταν κάνετε λεξικογραφική ανάλυση (στα αγγλικά parsing) σε βάσεις δεδομένων κειμένου όπου κάθε γραμμή είναι μια ξεχωριστή εγγραφή. Όμως, η πραγματική δύναμη αυτού αλλά και των υπόλοιπων βρόχων ξεδιπλώνεται όταν η λίστα με τα ορίσματα δημιουργείται on-the-fly μέσω κάποιας άλλης εντολής:

for file in $( find / -type l -name '*html')  
 do 
   #οτιδήποτε
 done

Η πρώτη εντολή παραπάνω είναι το μόνο που χρειάζεστε για να βρείτε και να επεξεργαστείτε σε πραγματικό χρόνο όλα τα αρχεία του συστήματος που έχουν επέκταση .html αλλά είναι απλώς παραπομπές (-type l, όπου l σημαίνει links) σε άλλες σελίδες.

Εντολές While και Until

Κάποιες φορές θα θέλετε να κάνετε κάτι σε κάθε στοιχείο ενός λίγο πολύ δεδομένου συνόλου. Άλλες φορές πρέπει να διεκπεραιώσετε μια ενέργεια σε άγνωστο πλήθος επαναλήψεων, μέχρι να συμβεί κάτι. Σε αυτές τις περιπτώσεις χρειάζεστε τις εντολές while και until. Η while ελέγχει μια συνθήκη στην κορυφή του βρόχου και συνεχίζει τις επαναλήψεις μέχρι η συνθήκη αυτή να γίνει ψευδής. Η until έχει την ίδια σύνταξη με την διαφορά ότι κάνει επαναλήψεις μέχρι η συνθήκη να γίνει αληθής.

while [ συνθήκη ]
  do
    #εντολές
  done

 

until [ συνθήκη ]
 do
   #εντολές
 done

Μπορείτε να τερματίσετε τις επαναλήψεις νωρίτερα εάν το θέλετε, με μία break ή μια continue δήλωση. Η break κάνει αυτό ακριβώς που σημαίνει: “σπάζει” το βρόχο της επανάληψης στον οποίο εμπεριέχεται. H continue απλά παρακάμπτει την τρέχουσα επανάληψη και έτσι δεν εκτελούνται οι επόμενες εντολές της.

Είναι εφικτό να βάλετε ένα βρόχο μέσα σε έναν άλλο (γνωστό ως εμφωλιασμός=nesting). Φυσικά, μια break θα έχει διαφορετικά αποτελέσματα, ανάλογα με την θέση της μέσα στις εμφωλιασμένες επαναλήψεις. Για να καταλάβετε καλύτερα, δοκιμάστε να βγάλετε τα σχόλια σε μία μόνο από τις break δηλώσεις στο παρακάτω παράδειγμα:

#! /bin/bash
A=0
while [[ “$A” -lt “5”  ]]
do
	echo A: $A
	if [  “$A” ==”2”  ]
	then
		echo “	Hello from the outer loop”
		#break
	else
		while [[  “$B” -lt “4”  ]] 
		do
			if [  “$A” ==”1”  ]
			then
				echo “	Hello from the inner loop”
				#break
			else
				echo “	B: $B”
			fi
			let B=$B+1
		done
	fi
	let A=$A+1
done

 

What if...

Μερικές φορές, ένα πρόγραμμα δεν πρέπει να κάνει επαναλήψεις με την ίδια πορεία κάθε φορά, αλλά να επιλέγει μια πορεία για να την εκτελέσει μία και μόνο φορά. Αυτό γίνεται με τη δομή if/then που αποφασίζει ποιο από τα δύο μονοπάτια να ακολουθήσει σε ένα διάγραμμα ροής, ανάλογα με τον κωδικό εξόδου μιας test ή μιας άλλης εντολής. Όσον αφορά την σύνταξη η if test $A -eq 1 ; then ... ; fi είναι ισοδύναμη με την if [ $A -eq 1 ]; then ... ; fi. Φυσικά, οι δομές if/then μπορούν να εμφωλιαστούν η μία μέσα στην άλλη. Στο παρακάτω παράδειγμα, η elif είναι συντομογραφία του else if:

if  [ Αληθής_Συνθήκη ]
then
	# εντολές
elif [[ Μια_Άλλη_Αληθής_Συνθήκη ]
then
	# εντολές
else
	# εντολές
fi

Μια δομή if/then είναι ότι πρέπει όταν υπάρχουν μόνο δύο πιθανές επιλογές, αλλά τι γίνεται στην περίπτωση που υπάρχουν περισσότερες; Θα μπορούσατε να χρησιμοποιήσετε πολλά τέτοια μπλοκ εμφωλιασμένα το ένα μέσα στο άλλο κατά κάποιο τρόπο, αλλά αυτή δεν είναι κομψή λύση. Ευτυχώς, υπάρχει η εντολή case.γι' αυτή τη περίπτωση. Αυτή είναι το αντίστοιχο της switch που χρησιμοποιεί κανείς στη C. Εάν είστε αρχάριοι στον προγραμματισμό μην ανησυχείτε. Το παρακάτω βασικό παράδειγμα ενός αλληλεπιδραστικού μενού δείχνει όλη την σύνταξη της εντολής:

while [  “$os” == “”  ]
do
	echo “Choose an operating system”
	echo “[L]inux”
	echo “[W]indows”
	echo 
	read os
	case “$os” in 
	 “L” | “l” )
	echo 'Excellent Choice!'
	;;
	“W” | “w” )
	echo 'Yuck!  Are you sure?'
	;;
	*)
	echo 'Come on make a choice!'
	;;
	esac
done

Η case λειτουργεί με μια μεταβλητή ελέγχου, όπως η $ο στο παραπάνω script. Οι διάφοροι κλάδοι του κώδικα διαχωρίζονται από διπλά ερωτηματικά. Καθένας ξεκινά με μια λίστα των πιθανών τιμών της μεταβλητής ελέγχου που θα πυροδοτήσουν την εκτέλεση των εντολών που ακολουθούν (π.χ. echo). Ο χαρακτήρας πίπα (|) δηλώνει ισοδυναμία των τιμών (στα μαθηματικά ένωση). Οι διάφορες πιθανές τιμές ελέγχονται από τα πάνω προς τα κάτω και εκτελείται ο πρώτος κλάδος που ταιριάζουν οι τιμές του με την μεταβλητή ελέγχου. Στο συγκεκριμένο, εάν πατήσετε 'L' ή 'l', η εκτέλεση θα σταματήσει γράφοντας “Excellent Choice” στην οθόνη. Εάν παρ' ελπίδα δώσετε 'w' ή 'W' τότε θα αγνοηθεί ο πρώτος κλάδος και τυπωθεί η κατάλληλη απάντηση. Η τελευταία επιλογή *) υπάρχει για την περίπτωση που η μεταβλητή δεν έχει κάποια τιμή από αυτές που αναφέρονται. Να βάζετε πάντα ένα τέτοιο default κλάδο για να τυπώνει ένα μήνυμα λάθους ή κάτι άλλο.

Συναρτήσεις

Όταν δοκιμάσετε κάποια από αυτές τις τεχνικές, είναι πιθανό να βρείτε κάποια μικρά (ή όχι και τόσο μικρά) κομμάτια επαναλαμβανόμενου κώδικα, στα οποία αλλάζουν μόνο λίγες παράμετροι. Μπορείτε να δομήσετε τον κώδικά σας ώστε να αποφύγετε αυτήν την επανάληψη και να κάνετε ευδιάκριτη τη ροή του.

Το πρώτο βήμα είναι να δημιουργήσετε μερικές συναρτήσεις κελύφους. Πρόκειται απλώς για κομμάτια κώδικα που υλοποιούν μια ξεχωριστή διαδικασία και πρέπει να έχετε “δηλώσει” πριν από την κλήση τους. Ο πιο καλός τρόπος να το κάνετε αυτό είναι:

Function_Name () {
  #εντολή
  #εντολή
  #εντολή
return
}

Για να τρέξετε μια συνάρτηση κελύφους, την καλείτε με το όνομά της προσθέτοντας τις πιθανές παραμέτρους στην σωστή σειρά. Όπως και με τα αυτόνομα scripts, αυτές οι παράμετροι είναι διαθέσιμες εντός της συνάρτησης μέσω των τυπικών μεταβλητών $1, $2 κτλ. Οι συναρτήσεις έχουν επίσης τοπικές ροές Εισόδου/Εξόδου, που μπορείτε να αλλάξετε την κατεύθυνση τους ή να τις τροφοδοτήσετε με HERE έγγραφα:

FunctionA <$file
FunctionB <<Function_data
Christmas
Easter
Function_data

Η πρώτη εντολή στέλνει στην FunctionA τα περιεχόμενα της $file. Η δεύτερη τρέχει την FunctionB με παραμέτρους Christmas και Easter με αυτή τη σειρά. Οι μεταβλητές μπορούν επίσης να οριστούν ως τοπικές μέσω της δήλωσης local και έτσι είναι εφικτή και η αναδρομή.

Τέλος, και οι συναρτήσεις του κελύφους επιστρέφουν κωδικούς εξόδου. Αυτός θα σχετίζεται με την τελευταία εντολή που εκτέλεσε η συνάρτηση ή με το όρισμα της δήλωσης return. Και πάλι, το script που κάλεσε την συνάρτηση θα βρει τον κωδικό εξόδου της στην $?.

Συμβουλή

Είναι χρήσιμο να βάλετε τον κώδικα που χρησιμοποιείτε συχνά σε ένα ξεχωριστό αρχείο, που μπορεί να φορτωθεί με μία απλή εντολή από οποιοδήποτε script που τον χρειάζεται. Με τις εντολές

. filename [ορίσματα]

ή

source filename [ορίσματα]

εκτελούνται οι εντολές που περιέχονται στο αρχείο filename ώστε να τις ελέγξετε πριν τρέξετε το script. Εάν υπάρχουν ορίσματα γίνονται παράμετροι όταν εκτελείται το filename. Εάν δεν υπάρχει το filename ή δεν είναι αναγνώσιμο θα επιστραφεί false.

 

Προγραμματισμός σε Bash:

Αν θέλετε να μάθετε περισσότερα για το scripting διαβάστε τους παρακάτω οδηγούς:

Εισαγωγή στο Bash: Ροές και pipes

Bash: Βρόχος επανάληψης σε εύρος αριθμών όπου μεταβλητές ορίζουν την αρχή ή το τέλος

Bash: Πως εκτελούμε μια εντολή Χ φορές στη σειρά με την for

Bash: Κωδικοί εξόδου και έλεγχος ροής

Scripting: Όταν το GUI δέν επαρκεί

Bash: Επεξεργασία κειμένου με Regular Expressions και άλλα κόλπα

Advanced Bash-Scripting Guide

Δώσε αστέρια!

MO: 5 (ψήφοι: 3)

Σχόλια

Πολυ καλο αλλα μια διορθωση...

η συνταξη της συνθηκης  if ειναι  if [ expression ]; then

απλα δεν εβαλες το ;

slackware]

Πολυ καλο αλλα μια διορθωση...

η συνταξη της συνθηκης  if ειναι  if [ expression ]; then

απλα δεν εβαλες το ;

Το έγραψα βρε:

Όσον αφορά την σύνταξη η if test $A -eq 1 ; then ... ; fi είναι ισοδύναμη με την if [ $A -eq 1 ]; then ... ; fi.

Απλά όταν γράφεις σκριπτάκι δουλεύουν και τα δύο:

#!/bin/bash 
if [ -f ~/test.sh ]
then
    echo "hello"
fi

και το παραπάνω δουλεύει και αυτό:

#!/bin/bash 
if [ -f ~/test.sh ]; then
    echo "hello"
fi

 

Α ναι? δεν το ειδα... Ακομη δεν ηπια τον καφε μου...!!!

Απο προσωπικη εμπειρια εχω χασει 3 ωρες γιατι ο (Σ)if(ης) δεν μου δουλευε σωστα και ηταν αιτια το ; .

Μιλαμε χρονια πριν οποτε τωρα το βαζω

Όταν τα διάφορα κομμάτια του if statement ή των loops «σπάνε» σε γραμμές, το ; περισσεύει

Σε παλια λειτουργικα ή σε λειτουργικα που τρεχουν αλλα shells ο κωδικας δεν θα δουλευει χωρις το ;

Δοκίμασε να τρεξεις σε bash του 2004, 2005 κλπ και θα δεις ότι χωρίς το ; Δεν θα παίζει οπότε είναι θέμα portability πιο πολύ για μενα

Το Bash του 2004 ή 2005 θεωρείται πανάρχαιο για τα σημερινά δεδομένα :p

Συμφωνω αλλα ποτε δεν ξερεις που θα χρειαστεις να γραψεις καποιο script οποτε καλο ειναι να ξερεις πως. (κατα την αποψη μου).

 

Προσωπικα, γραφω scripts σε bash με το παλιο δοκιμασμενο στυλ ωστε να μην χρειαστει να το ξαναγραφω.

Έχεις δίκιο φίλε μου. Να σημειώσω ότι με την έννοια «πανάρχαιο» δεν εννοούσα σε δυνατότητες, αλλά στην έκδοση και ο μόνος λόγος πλέον να τρέχεις παλιό Bash είναι να χρησιμοποιείς κάποια απαρχαιωμένη διανομή σε παλιό μηχάνημα, αλλά και 'κεί ακόμα μου έχει τύχει π.χ. να περάσω Arch σε laptop του 2002 με το τελευταίο Bash (και Kernel) και το μηχάνημα να «φυσάει», τώρα αν μιλάμε για μηχάνημα που δεν σηκώνει τίποτα παραπάνω από Kernel 2.4, η γνώμη μου είναι ότι δεν αξίζει καν το ρεύμα που καίει, ειδικά αν σκεφτείς πόσο έχει «βαρύνει» πλέον ακόμα και το απλό σερφάρισμα στο Internet (δυστυχώς).

Helix]

Έχεις δίκιο φίλε μου. Να σημειώσω ότι με την έννοια «πανάρχαιο» δεν εννοούσα σε δυνατότητες, αλλά στην έκδοση και ο μόνος λόγος πλέον να τρέχεις παλιό Bash είναι να χρησιμοποιείς κάποια απαρχαιωμένη διανομή σε παλιό μηχάνημα, αλλά και 'κεί ακόμα μου έχει τύχει π.χ. να περάσω Arch σε laptop του 2002 με το τελευταίο Bash (και Kernel) και το μηχάνημα να «φυσάει», τώρα αν μιλάμε για μηχάνημα που δεν σηκώνει τίποτα παραπάνω από Kernel 2.4, η γνώμη μου είναι ότι δεν αξίζει καν το ρεύμα που καίει, ειδικά αν σκεφτείς πόσο έχει «βαρύνει» πλέον ακόμα και το απλό σερφάρισμα στο Internet (δυστυχώς).

Υπάρχει και άλλος λόγος να τρέχεις τόσο παλιό Bash, θα σου πει ο slackware. Π.χ. να συντηρείς servers που τρέχουν αδιαλείπτως από το 2004... :) Και φυσικά τα συγκεκριμένα μηχανήματα αξίζουν και με το παραπάνω τα λεφτά που (έχουν κάψει) καίνε... Ετσι δεν είναι mr. slackware ? :P

Καλά εντάξει για servers δεν το συζητώ, εγώ αναφερόμουν στα desktops... Ακόμα και 'κεί όμως, όλο και κάποια αναβάθμιση θα έχει φάει ένας server που δουλεύει αδιάλειπτα από το... 2004 :p

Helix]

Καλά εντάξει για servers δεν το συζητώ, εγώ αναφερόμουν στα desktops... Ακόμα και 'κεί όμως, όλο και κάποια αναβάθμιση θα έχει φάει ένας server που δουλεύει αδιάλειπτα από το... 2004 :p

Αν εννοείς hardware ε ναι. Αν εννοείς software συμβαίνει μερικές φορές να θέλει κάποιος να τρέχει το ίδιο λειτουργικό (ή έστω μια πολύ παλιά version) στο ίδιο server γιατί του κάνει κάποια συγκεκριμένη δουλειά.

Εννοούσα software βασικά... Ακόμα και το Debian πάντως, μία διανομή που προτιμάται πολύ στους servers λόγω της σταθερότητας της, βγάζει κάθε νέα έκδοση που θεωρείται stable as rock κάθε 2 χρόνια

Helix]

Εννοούσα software βασικά... Ακόμα και το Debian πάντως, μία διανομή που προτιμάται πολύ στους servers λόγω της σταθερότητας της, βγάζει κάθε νέα έκδοση που θεωρείται stable as rock κάθε 2 χρόνια

Σωστός. Απλώς κάποιοι τρέχουν παλιό software οπότε κρατάνε παλιές διανομές κι όσο πάει.

Ακριβώς, αρκεί να υπάρχουν ενημερώσεις ασφαλείας που είναι το βασικότερο σε τέτοιες περιπτώσεις. Γι' αυτό πολλοί προτιμούν να εγκαταστήσουν σε servers διανομές με Long Term Support.

Στην δουλεια μου εχω δει πολλα παλια λειτουργικα (Solaris 2.5.6 2.5.7 μεχρι και 2.5.1!!!) οποτε δεν θα μου εκανε εντυπωση οτι μπορει να υπαρχουν και σερβερς που να τρεχουν παλιες Linux distros.

 

Οσο αφορα τα Patches, αν εχεις ενα σωστο security team, δεν θα βαζει οτι patch βγει αλλα θα το ελεγχει πρωτα, και αν εχει βαλει τις σωστες βασεις τοτε δυσκολα μπαινει καποιος.

 

Εχω δει εφαρμογες που τρεχουν στα παραπανω λειτουργικα που ανεφερα, ο λογος που δεν πανε σε νεοτερο ειναι γιατι ο χρονος και το κοστος της επανασυγραφης της εφαρμογης σε σημερινα δεδομενα θα ειναι ο ιδιος ή και περισσοτερος με το να την φτιαξουν απο την αρχη. (Οτι δουλευει ΔΕΝ το πειραζουμε)

Αυτα!