Comandi interni di Bash vs. comandi esterni: un confronto sulle prestazioni
Accelera gli script Bash utilizzando i comandi interni della shell per test, operazioni aritmetiche e manipolazione di stringhe, raggruppando i comandi esterni in batch.
Comandi interni di Bash vs. comandi esterni: un confronto sulle prestazioni
Quando uno script Bash sembra lento, la causa è spesso un ciclo che avvia migliaia di processi esterni. La scelta tra comandi interni di Bash e comandi esterni è una decisione pratica per le prestazioni: usa le funzionalità native della shell per operazioni semplici e riserva gli strumenti esterni per compiti che gestiscono meglio.
Questa guida mostra dove i comandi interni aiutano, dove i comandi esterni hanno ancora senso e come evitare le trappole più comuni legate alla creazione di processi.
Comprendere l'esecuzione dei comandi in Bash
Quando Bash incontra un comando, risolve alias, parole chiave della shell, funzioni, comandi interni e poi comandi trovati tramite PATH. Questa risoluzione è importante perché tutto ciò che viene gestito all'interno della shell corrente evita di avviare un programma separato.
1. Comandi interni
I comandi interni di Bash sono funzioni implementate direttamente all'interno dell'eseguibile della shell Bash. Non richiedono di invocare le chiamate di sistema fork() e exec() del sistema operativo. Poiché l'esecuzione avviene interamente all'interno del processo shell esistente, i comandi interni offrono prestazioni superiori, overhead minimo e accesso immediato alle variabili e allo stato della shell.
Caratteristiche principali dei comandi interni:
- Velocità: Percorso di esecuzione più veloce.
- Overhead: Overhead quasi nullo, poiché non viene creato alcun nuovo processo.
- Ambiente: Operano direttamente sull'ambiente della shell corrente.
2. Comandi esterni
I comandi esterni sono file eseguibili separati (spesso situati in directory come /bin, /usr/bin, ecc.). Quando Bash esegue un comando esterno, deve:
fork()un nuovo processo figlio.exec()il programma esterno all'interno di quel processo figlio.- Attendere il completamento del processo figlio.
Questo overhead, sebbene trascurabile per una singola esecuzione, si accumula rapidamente in cicli o operazioni ad alta frequenza, rendendo i comandi esterni significativamente più lenti rispetto alle loro controparti interne.
Il confronto delle prestazioni: comandi interni in azione
Per illustrare la differenza di prestazioni, considera attività comuni in cui Bash fornisce sia un'alternativa interna che una esterna.
Esempio 1: Manipolazione di stringhe e calcolo della lunghezza
Calcolare la lunghezza di una variabile è un classico caso di test delle prestazioni.
| Tipo di comando | Comando | Descrizione |
|---|---|---|
| Interno | ${#variable} |
Espansione dei parametri per la lunghezza. Estremamente veloce. |
| Esterno | expr length "$variable" |
Invoca l'utilità esterna expr. Lento. |
Consiglio sulle prestazioni: Usa sempre l'espansione dei parametri (${#var}) per il calcolo della lunghezza invece di expr length o di un pipe a wc -c.
Esempio 2: Sostituzione di stringhe
Sostituire sottostringhe all'interno di una variabile è un'altra operazione comune.
| Tipo di comando | Comando | Descrizione |
|---|---|---|
| Interno | ${variable//pattern/replacement} |
Sostituzione tramite espansione dei parametri. Veloce. |
| Esterno | sed 's/pattern/replacement/g' |
Invoca l'utilità esterna sed. Lento. |
Confronto di codice di esempio:
TEXT="hello world hello"
# Interno (Veloce)
NEW_TEXT_1=${TEXT//hello/goodbye}
# Esterno (Lento)
NEW_TEXT_2=$(echo "$TEXT" | sed 's/hello/goodbye/g')
Esempio 3: Cicli e iterazioni
Quando si itera, il comando utilizzato all'interno del ciclo è estremamente importante.
| Tipo di comando | Comando | Descrizione |
|---|---|---|
| Interno | read |
Utilizzato per leggere l'input riga per riga in modo efficiente. |
| Esterno | grep, awk, cut |
Inviare dati a strumenti esterni all'interno di un ciclo forza la creazione ripetuta di processi. |
L'anti-pattern while read vs. comandi interni:
Un pattern lento comune è inviare il contenuto di un file a comandi esterni all'interno di un ciclo:
# LENTO: Avvia 'grep' per ogni singola riga
while read LINE; do
echo "Elaborazione: $LINE" | grep "importante"
done < input.txt
Strategia di ottimizzazione: Se possibile, usa i comandi interni di Bash o la reindirizzazione interna per evitare comandi esterni all'interno dei cicli.
Comandi interni chiave di Bash per le prestazioni
Dare priorità a questi comandi interni rispetto alle loro equivalenti esterne porterà a miglioramenti significativi della velocità nei tuoi script:
| Categoria di attività | Comando interno | Alternativa esterna (più lenta) |
|---|---|---|
| Aritmetica | (( espressione )) |
expr, bc |
| Test sui file | [[ ... ]] o il comando interno [ ... ] di Bash |
/usr/bin/test o /usr/bin/[ esterni |
| Manipolazione di stringhe | ${var/pat/rep}, ${#var} |
sed, awk, expr |
| Cicli/Lettura di file | read |
grep, awk, sed (se usati iterativamente) |
| Caricamento di codice shell | source o . nomefile |
Eseguire un altro script come processo figlio |
Esempio aritmetico
Interno (Veloce):
COUNTER=0
(( COUNTER++ ))
if (( COUNTER > 10 )); then echo "Fatto"; fi
Esterno (Lento):
COUNTER=$(expr "$COUNTER" + 1)
if [ "$COUNTER" -gt 10 ]; then echo "Fatto"; fi
Quando i comandi esterni sono necessari
Sebbene i comandi interni dovrebbero essere la scelta predefinita per le operazioni di base, le utilità esterne rimangono essenziali per compiti che Bash non può gestire nativamente o in modo efficiente. Devi usare comandi esterni quando:
- Elaborazione avanzata del testo: Pattern matching complesso, manipolazione multi-riga o formattazione specifica offerta da strumenti come
awk,sedoperl. - Utilità di sistema: Comandi che interagiscono profondamente con il sistema operativo, come
ls,ps,find,mounto strumenti di rete (curl,ping). - File esterni: Leggere o scrivere file in formati complessi con cui la reindirizzazione di Bash ha difficoltà.
Buona pratica per l'uso di comandi esterni
Se devi usare un comando esterno, cerca di minimizzare il numero di volte in cui viene invocato. Invece di eseguire un comando esterno all'interno di un ciclo, ristruttura la logica per elaborare l'intero batch di dati in una singola chiamata esterna.
Inefficiente: Elaborare 1000 file individualmente con stat.
Efficiente: Usare una singola chiamata a find combinata con stat o un singolo script awk per raccogliere tutti i metadati necessari in una volta sola.
Conclusione
L'ottimizzazione delle prestazioni in Bash inizia con l'evitare la creazione non necessaria di processi. Usa per impostazione predefinita i comandi interni per operazioni aritmetiche, test e semplici manipolazioni di stringhe. Quando uno strumento esterno è lo strumento giusto, eseguilo una volta su un batch invece che una volta per riga o per file.
- Usa per impostazione predefinita i comandi interni: Per operazioni aritmetiche (
(( ))), manipolazione di stringhe (${...}) e test ([[ ]]), scegli sempre il comando interno della shell. - Evita I/O nei cicli: Ristruttura i cicli per eseguire l'elaborazione in batch usando una singola chiamata a un comando esterno invece di molte chiamate piccole.
- Usa l'espansione dei parametri: Preferisci
${#var}awcoexprper la lunghezza delle stringhe. - Riconosci i compromessi: Usa utilità esterne quando la funzionalità richiesta non è disponibile o è scomoda in Bash.