DedSec Project

bash — συγκεντρωτικός οδηγός — DedSec Project

bash — συγκεντρωτικός οδηγός Bash Συνήθης χρήση: Αυτοματισμός και σενάρια. Προτεινόμενη επέκταση αρχείου: .sh . Εκτέλεση / περιβάλλον: bash hello.sh. Αρχικό παράδειγμα Πώς διαβάζεται Βάζε…

Bash

Συνήθης χρήση: Αυτοματισμός και σενάρια.

Προτεινόμενη επέκταση αρχείου: .sh.

Εκτέλεση / περιβάλλον: bash hello.sh.

Αρχικό παράδειγμα

#!/usr/bin/env bash
set -euo pipefail
greet() { printf 'Hello, %s!\n' "$1"; }
greet 'Ada'

Πώς διαβάζεται

Βάζε εισαγωγικά στις μεταβλητές κελύφους για αποφυγή λανθασμένου διαχωρισμού.

Συνηθισμένο λάθος

Βάζε εισαγωγικά στις μεταβλητές κελύφους για αποφυγή λανθασμένου διαχωρισμού. Έλεγξε την έκδοση διερμηνευτή/μεταγλωττιστή και μην εκλαμβάνεις το δείγμα ως έτοιμη εφαρμογή παραγωγής.

Shell scripts που σέβονται τις διαδρομές

Τα shell scripts κυρίως συνδυάζουν διεργασίες. Βάλε quotes σε expansions για filenames με κενά: "$file", όχι $file. Η Bash υποστηρίζει arrays και επιλογές εργαλείων χωριστά από filenames.

#!/usr/bin/env bash
set -euo pipefail
dir="${1:-.}"
if [[ ! -d "$dir" ]]; then
  printf 'Not a directory: %s\n' "$dir" >&2
  exit 2
fi
while IFS= read -r -d '' file; do
  printf 'Found: %s\n' "$file"
done < <(find "$dir" -maxdepth 1 -type f -print0)

Το set -e έχει εξαιρέσεις και δεν αντικαθιστά σχεδιασμό χειρισμού σφάλματος. Μία εντολή σε pipeline μπορεί να αποτύχει χωρίς αναμενόμενο αποτέλεσμα. Filenames μπορούν να ξεκινούν από -: βάλε -- όπου υποστηρίζεται. -print0 αντιμετωπίζει ασυνήθιστα ονόματα.

Άσκηση: δημιούργησε σε προσωρινό φάκελο ονόματα με κενά και αλλαγές γραμμής και απέδειξε ότι εμφανίζονται μία φορά. Πηγή: https://www.gnu.org/software/bash/manual/.

Bash: quotes, exit codes και διαδρομές με κενά

Τα shell scripts συχνά αποτυγχάνουν όταν ένα path χωρίζεται σε λέξεις ή αγνοείται το exit status. Αποθήκευσε το παρακάτω ως script και τρέξ' το μόνο σε προσωρινό φάκελο δικής σου δημιουργίας:

#!/usr/bin/env bash
set -euo pipefail
main() {
  if (($# != 1)); then printf 'usage: %s DIR\n' "$0" >&2; return 2; fi
  local root=$1
  [[ -d "$root" ]] || { printf 'not a directory: %s\n' "$root" >&2; return 2; }
  find "$root" -maxdepth 1 -type f -print
}
main "$@"

Δώσε όρισμα "demo folder" μέσα σε εισαγωγικά και δες ότι τα αρχεία εμφανίζονται σωστά. Δοκίμασε ανύπαρκτο φάκελο: αναμένεται status 2. Σύγκρινε το "$@" με το σκέτο $@ και το "$root" με το $root όταν υπάρχουν κενά ή wildcard. Η έξοδος find -print χωρίζει με newline· για αυτοματοποίηση με αυθαίρετα ονόματα καλύτερα -print0 και NUL-aware επεξεργασία.

Το set -e έχει εξαιρέσεις σε conditionals, command substitutions και pipelines. Το pipefail βοηθά με αποτυχία ενδιάμεσου command αλλά δεν αντικαθιστά τους λογικούς ελέγχους. Για προσωρινά αρχεία χρησιμοποίησε mktemp και trap που αφαιρεί μόνο δικά σου paths.

Κριτήριο: σωστά ονόματα με κενά, σαφές σφάλμα για άκυρο φάκελο και καμία τροποποίηση εκτός δοκιμαστικού χώρου. Πηγή: https://www.gnu.org/software/bash/manual/.

Bash — Αναλυτικός οδηγός εξάσκησης

Σύμβαση εισόδου

Μέτρα μη κενές γραμμές που δεν αρχίζουν με #. Συμπερίλαβε την τελευταία γραμμή ακόμη και χωρίς newline. Μην εκτελείς ποτέ το περιεχόμενο ως shell code και μην τροποποιείς το αρχείο.

Εκτέλεση

Αποθήκευσε count.sh και τρέξε bash count.sh "example file.txt". Γράψε alpha, γραμμή με # στην αρχή, κενή γραμμή και beta. Περιμένεις useful lines: 2. Η διαδρομή μπορεί να περιέχει κενά.

#!/usr/bin/env bash
set -euo pipefail
if (( $# != 1 )); then
  printf 'Usage: bash count.sh FILE\n' >&2
  exit 2
fi
file=$1
if [[ ! -f $file || ! -r $file ]]; then
  printf 'File missing or unreadable: %s\n' "$file" >&2
  exit 1
fi
count=0
while IFS= read -r line || [[ -n $line ]]; do
  [[ -z $line || $line == \#* ]] && continue
  ((count+=1))
done < "$file"
printf 'useful lines: %d\n' "$count"

Γιατί λειτουργεί

Τα εισαγωγικά γύρω από "$file" διατηρούν το όνομα ως ένα όρισμα. Τα IFS= read -r διατηρούν κενά και ανάστροφες καθέτους. Η δεύτερη συνθήκη του while χειρίζεται τελευταία γραμμή χωρίς newline. Με set -e, το ((count++)) μπορεί να αποτύχει στην αρχή επειδή αξιολογείται σε μηδέν· το ((count+=1)) αποφεύγει αυτή την παγίδα.

Προκλήσεις

Δοκίμασε άδειο αρχείο, κατάλογο αντί για αρχείο, ανύπαρκτη διαδρομή, τελευταία γραμμή χωρίς newline και σχόλια με αρχικά κενά. Εδώ το # πρέπει να βρίσκεται στην πρώτη στήλη. Αν αλλάξεις τον κανόνα, γράψε ξεχωριστά tests. Δες κωδικό εξόδου με echo $?.

Περιβάλλοντα

Χρησιμοποίησε bash και όχι sh επειδή τα [[...]] και pipefail δεν είναι κοινά σε κάθε shell. Σε Termux έλεγξε bash και δικαιώματα αποθήκευσης. Τα Windows απαιτούν Bash περιβάλλον. Σε αρχεία CRLF ενδέχεται να παραμένει carriage return και χρειάζεται ρητή πολιτική μετατροπής. Επιτυχία: 2, χωρίς αλλαγή του αρχείου ή προβλήματα σε διαδρομές με κενά. https://www.gnu.org/software/bash/manual/

Bash — Παραδείγματα πραγματικού κώδικα

Σενάριο

Θέλουμε να αντιγράψουμε .txt αρχεία σε ξεχωριστό φάκελο. Ένα αξιόπιστο script οφείλει να διαχειρίζεται κενό κατάλογο, filenames με spaces και αποτυχία αντιγραφής χωρίς να αγγίζει άλλα αρχεία.

#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
source_dir="${1:?missing source directory}"
target_dir="${2:?missing target directory}"
[[ -d "$source_dir" ]] || { printf 'missing source\n' >&2; exit 2; }
mkdir -p -- "$target_dir"
files=( "$source_dir"/*.txt )
if (("${#files[@]}" == 0)); then
  printf 'No matching files\n'
  exit 0
fi
for file in "${files[@]}"; do
  cp -- "$file" "$target_dir/"
done
printf 'Copied %d files\n' "${#files[@]}"

Αποθήκευσε ως backup.sh. Σε δοκιμαστικό κατάλογο δημιούργησε a b.txt και notes.md. Το bash backup.sh input output πρέπει να αντιγράψει μόνο το .txt και να διατηρήσει το όνομα.

Τι προστατεύει

Το "${files[@]}" κρατά χωριστά τα filenames. Το nullglob εμποδίζει το κυριολεκτικό *.txt αν δεν υπάρχουν αποτελέσματα. Το -- σταματά την ερμηνεία option flags σε cp/mkdir. Το set -e δεν καλύπτει κάθε σύνθετο condition και δεν είναι υποκατάστατο δοκιμών.

Εργαστήριο

Πρόσθεσε dry-run flag που εμφανίζει πηγές και στόχους χωρίς αντιγραφή. Έλεγξε κενό input, read-only output, filename με leading dash και δύο αρχικά αρχεία με διαφορετικές καταλήξεις. Χρησιμοποίησε προσωρινά test δεδομένα και κατέγραψε exit code όταν αποτύχει μία αντιγραφή.

Bash — Διαδρομή ουσιαστικής εξάσκησης

Στόχος και απαιτήσεις

Σε Linux ή Termux, εμφάνισε τα κανονικά αρχεία ενός καταλόγου με σχετική διαδρομή, μέγεθος σε byte και σταθερό πλήθος. Ονόματα με κενά, ελληνικά γράμματα, στηλοθέτες ή αλλαγές γραμμής πρέπει να παραμένουν αναγνωρίσιμα. Δεν αλλάζουμε τα εξεταζόμενα αρχεία και δεν απαιτούμε sudo. Χρειάζονται GNU find -print0, sort -z, wc, mktemp και η μορφή %q της Bash. Δεν ακολουθούμε συμβολικούς συνδέσμους.

Εκτελέσιμο πρόγραμμα

Αποθήκευσε ως inventory.sh, έλεγξε με bash -n inventory.sh και εκτέλεσε bash inventory.sh 'fixture [demo]' σε προσωρινό κατάλογο. Τα βοηθητικά αρχεία δημιουργούνται έξω από τον κατάλογο που εξετάζεις και διαγράφονται στο τέλος.

#!/usr/bin/env bash
set -euo pipefail
[[ $# -eq 1 && -d $1 ]] || { printf 'usage: %s DIRECTORY\n' "$0" >&2; exit 2; }
root=$(cd -- "$1" && pwd -P)
tmp=$(mktemp -d) || exit 1
trap 'rm -rf -- "$tmp"' EXIT
# GNU sort -z and find -print0 preserve whitespace and newlines in file names.
find "$root" -type f -print0 > "$tmp/files"
LC_ALL=C sort -z "$tmp/files" > "$tmp/sorted"
count=0
while IFS= read -r -d '' file; do
  [[ -f $file ]] || { printf 'vanished: %q\n' "$file" >&2; exit 3; }
  bytes=$(wc -c < "$file")
  relative=${file#"$root"/}
  printf '%q\t%d\n' "$relative" "$bytes"
  ((count+=1))
done < "$tmp/sorted"
printf 'COUNT\t%d\n' "$count"

Εξήγηση των παγίδων

Η έκφραση for f in $(ls) είναι ανασφαλής: η υποκατάσταση εντολής αφαιρεί τελικές αλλαγές γραμμής, ενώ ο διαχωρισμός λέξεων και η ανάπτυξη συμβόλων αλλοιώνουν ονόματα αρχείων. Τα εισαγωγικά αργότερα δεν διορθώνουν την απώλεια. Ο χαρακτήρας NUL δεν υπάρχει μέσα σε διαδρομή, επομένως ο συνδυασμός find -print0, sort -z και read -r -d '' διατηρεί τα πραγματικά ονόματα. Η %q επιτρέπει να εμφανίζονται χωρίς ασάφεια ακόμη και ονόματα με αλλαγές γραμμής. Τα ενδιάμεσα αρχεία βοηθούν επίσης να εντοπίζονται αποτυχίες της find και της sort.

Περιορισμένες ασκήσεις

  1. Δημιούργησε 'space name.txt', 'α beta.txt', κενό αρχείο και αρχείο μέσα σε υποκατάλογο. Ο μετρητής πρέπει να είναι τέσσερα και το κενό αρχείο να έχει μηδέν byte.
  2. Πρόσθεσε αλλαγή γραμμής μέσα σε όνομα και σύγκρινε την %q με την απλή printf %s.
  3. Αφαίρεσε δοκιμαστικό αρχείο κατά τη σάρωση ή χρησιμοποίησε περιορισμένα δικαιώματα όπου εφαρμόζονται. Η αποτυχία πρέπει να είναι ορατή.
  4. Σύγκρινε δύο εκτελέσεις με αμετάβλητα δεδομένα. Χρησιμοποίησε ShellCheck μόνο αν υπάρχει και έλεγξε αν υποστηρίζεται η GNU sort -z.

Παραδοτέα: έξοδος προγράμματος, εκδόσεις Bash και sort, τέσσερα οριακά ονόματα και εξήγηση της χρήσης NUL.

Bash — Τεχνικά σημεία και συχνές παγίδες

Expansion και διαχωρισμός λέξεων

Η αποτίμηση παραμέτρων μπορεί να ακολουθείται από word splitting και globbing. Χρησιμοποίησε "$path" για μία διαδρομή και "${array[@]}" για πολλά χωριστά στοιχεία. Το "${array[*]}" παράγει μία συμβολοσειρά εντός εισαγωγικών και δεν είναι ισοδύναμο.

#!/usr/bin/env bash
set -euo pipefail
names=( "one file.txt" "-two.txt" )
for item in "${names[@]}"; do printf '<%s>\n' "$item"; done
printf 'count=%d\n' "${#names[@]}"

Pipelines

Το false | cat συνήθως επιστρέφει τον κωδικό του τελευταίου προγράμματος. Με pipefail αποτυγχάνει αν αποτύχει οποιοδήποτε στοιχείο. Έλεγξε εντολές με if command; then ... else ... fi όταν χρειάζεσαι ορατή, διαχειρίσιμη αποτυχία. Το set -e έχει εξαιρέσεις σε conditions και functions· μην υποθέτεις ότι κάθε nonzero σταματά αυτομάτως το script.

Υποχρεωτικές παράμετροι

Το set -u αποκαλύπτει χρήση unset variables. Το "${1:?missing argument}" παράγει σαφές μήνυμα όταν λείπει όρισμα. Το "${HOME:-}" επιλέγει ρητά fallback. Μην διατηρείς κρυφές defaults για διαδρομές στις οποίες γίνονται διαγραφές.

Portability και εργαστήριο

Το [[ ... ]] και τα arrays είναι χαρακτηριστικά Bash, όχι POSIX sh. Τρέξε bash -n script.sh πριν από runtime tests και shellcheck αν υπάρχει. Σύγκρινε bash script.sh με sh script.sh μόνο σε αναλώσιμο παράδειγμα και τεκμηρίωσε γιατί το shebang δεν ελέγχει τον interpreter όταν τον καλείς ρητά με sh. Έλεγξε filenames με spaces, empty arrays και αρχική παύλα.