Sostituzione della variabile Bash vs dirname e basename

Il prossimo script

str=/aaa/bbb/ccc.txt echo "str: $str" echo ${str##*/} == $(basename $str) echo ${str%/*} == $(dirname $str) 

produce:

 str: /aaa/bbb/ccc.txt ccc.txt == ccc.txt /aaa/bbb == /aaa/bbb 

La domanda è:

  • Negli script di bash, quando è consigliabile utilizzare i comandi dirname e basename e quando le sostituzioni delle variabili e perché?

Chiedendo principalmente perché:

 str="/aaa/bbb/ccc.txt" count=10000 s_cmdbase() { let i=0 while(( i++ < $count )) do a=$(basename $str) done } s_varbase() { let i=0 while(( i++ < $count )) do a=${str##*/} done } s_cmddir() { let i=0 while(( i++ < $count )) do a=$(dirname $str) done } s_vardir() { let i=0 while(( i++ < $count )) do a=${str%/*} done } time s_cmdbase echo command basename echo =================================== time s_varbase echo varsub basename echo =================================== time s_cmddir echo command dirname echo =================================== time s_vardir echo varsub dirname 

sul mio sistema produce:

 real 0m33.455s user 0m10.194s sys 0m18.106s command basename =================================== real 0m0.246s user 0m0.237s sys 0m0.007s varsub basename =================================== real 0m30.562s user 0m10.115s sys 0m17.764s command dirname =================================== real 0m0.237s user 0m0.226s sys 0m0.007s varsub dirname 

Chiamare programmi esterni (biforcarsi) costa tempo. Il punto principale della domanda è:

  • Ci sono delle insidie ​​che usano sostituzioni variabili invece di comandi esterni?

I comandi esterni apportano alcune correzioni logiche. Controlla il risultato del prossimo script:

 doit() { str=$1 echo -e "string $str" cmd=basename [[ "${str##*/}" == "$($cmd $str)" ]] && echo "$cmd same: ${str##*/}" || echo -e "$cmd different \${str##*/}\t>${str##*/}<\tvs command:\t>$($cmd $str)<" cmd=dirname [[ "${str%/*}" == "$($cmd $str)" ]] && echo "$cmd same: ${str%/*}" || echo -e "$cmd different \${str%/*}\t>${str%/*}<\tvs command:\t>$($cmd $str)<" echo } doit /aaa/bbb/ doit / doit /aaa doit aaa doit aaa/ doit aaa/xxx 

con il risultato

 string /aaa/bbb/ basename different ${str##*/} >< vs command: >bbb< dirname different ${str%/*} >/aaa/bbb< vs command: >/aaa< string / basename different ${str##*/} >< vs command: >/< dirname different ${str%/*} >< vs command: >/< string /aaa basename same: aaa dirname different ${str%/*} >< vs command: >/< string aaa basename same: aaa dirname different ${str%/*} >aaa< vs command: >.< string aaa/ basename different ${str##*/} >< vs command: >aaa< dirname different ${str%/*} >aaa< vs command: >.< string aaa/xxx basename same: xxx dirname same: aaa 

Uno dei risultati più interessanti è $(dirname "aaa") . Il comando esterno dirname ritorna correttamente . ma l'espansione variabile ${str%/*} restituisce il valore errato aaa .

Presentazione alternativa

script:

 doit() { strings=( "[[$1]]" "[[$(basename "$1")]]" "[[${1##*/}]]" "[[$(dirname "$1")]]" "[[${1%/*}]]" ) printf "%-15s %-15s %-15s %-15s %-15s\n" "${strings[@]}" } printf "%-15s %-15s %-15s %-15s %-15s\n" \ 'file' 'basename $file' '${file##*/}' 'dirname $file' '${file%/*}' doit /aaa/bbb/ doit / doit /aaa doit aaa doit aaa/ doit aaa/xxx doit aaa// 

Produzione:

 file basename $file ${file##*/} dirname $file ${file%/*} [[/aaa/bbb/]] [[bbb]] [[]] [[/aaa]] [[/aaa/bbb]] [[/]] [[/]] [[]] [[/]] [[]] [[/aaa]] [[aaa]] [[aaa]] [[/]] [[]] [[aaa]] [[aaa]] [[aaa]] [[.]] [[aaa]] [[aaa/]] [[aaa]] [[]] [[.]] [[aaa]] [[aaa/xxx]] [[xxx]] [[xxx]] [[aaa]] [[aaa]] [[aaa//]] [[aaa]] [[]] [[.]] [[aaa/]] 
  1. dirname output . se il suo parametro non contiene una barra / , quindi dirname emulatoria con sostituzione di parametro non produce gli stessi risultati a seconda dell’input.

  2. basename prende un suffisso come secondo parametro che rimuoverà anche questo componente dal nome del file. Puoi anche emulare questo usando la sostituzione dei parametri, ma poiché non puoi eseguirli entrambi contemporaneamente non è così breve come quando usi basename .

  3. L’uso di dirname o basename richiede una subshell poiché non sono incorporati nella shell, quindi la sostituzione dei parametri sarà più veloce, specialmente quando li si chiama in loop (come mostrato).

  4. Ho visto basename in diverse posizioni su sistemi diversi ( /usr/bin , /bin ) quindi se devi usare percorsi assoluti nello script per qualche motivo potrebbe rompersi poiché non riesce a trovare l’eseguibile.

Quindi, sì, ci sono alcune cose da considerare e, a seconda della situazione e dell’input, uso entrambi i metodi.

EDIT: Entrambi dirname e basename sono effettivamente disponibili come bash caricabili builtin sotto examples/loadables nell’albero dei sorgenti e possono essere abilitati (una volta compilati) usando

 enable -f /path/to/dirname dirname enable -f /path/to/basename basename 

La trappola principale nell’uso delle sostituzioni variabili è che possono essere difficili da leggere e supportare.

Questo è, ovviamente, soggettivo! Personalmente io uso sostituzioni variabili in tutto il luogo. Io uso read , IFS e set invece di awk . Uso le espressioni regolari di bash e colpisco il globbing esteso invece di sed . Ma questo perché:

a) Voglio prestazioni

b) Sono l’unica persona che vedrà mai questi script

È triste dire che molte persone che devono mantenere script di shell conoscono abbastanza poco la lingua. Devi prendere una decisione di equilibrio: che è più importante, le prestazioni o la manutenibilità? Nella maggior parte dei casi troverai che la manutenibilità vince.

Devi ammettere che basename $0 è abbastanza ovvio, mentre ${0##*/} è abbastanza oscuro