Entrambi hanno le loro stranezze, sfortunatamente.
Entrambi sono richiesti da POSIX, quindi la differenza tra loro non è un problema di portabilità¹.
Il modo semplice di utilizzare le utility è
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Nota le doppie virgolette attorno alle sostituzioni variabili, come sempre, e anche --
dopo il comando, nel caso in cui il nome del file inizi con un trattino (altrimenti i comandi interpreterebbero il nome del file come opzione). Ciò non riesce ancora in un caso limite, che è raro ma potrebbe essere forzato da un utente maligno²: la sostituzione dei comandi rimuove le nuove righe finali. Quindi, se un nome di file si chiama foo/bar
allora base
sarà impostato bar
al posto di bar
. Una soluzione alternativa consiste nell'aggiungere un carattere non newline e rimuoverlo dopo la sostituzione del comando:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Con la sostituzione dei parametri, non ci si imbatte in casi limite legati all'espansione di caratteri strani, ma ci sono una serie di difficoltà con il carattere barra. Una cosa che non è affatto un caso limite è che il calcolo della parte della directory richiede un codice diverso per il caso in cui non esiste /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Il caso limite è quando c'è una barra finale (incluso il caso della directory radice, che è tutta barra). I comandi basename
e dirname
rimuovono le barre finali prima che facciano il loro lavoro. Non è possibile rimuovere le barre finali in una volta sola se ci si attacca ai costrutti POSIX, ma è possibile farlo in due passaggi. Devi occuparti del caso quando l'input è costituito solo da barre.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Se ti capita di sapere che non sei in un caso limite (ad es. Un find
risultato diverso dal punto iniziale contiene sempre una parte di directory e non ha tracce /
), la manipolazione della stringa di espansione dei parametri è semplice. Se devi affrontare tutti i casi limite, le utility sono più facili da usare (ma più lente).
A volte, potresti voler trattare foo/
come foo/.
piuttosto che come foo
. Se stai agendo su una voce della directory, allora foo/
dovrebbe essere equivalente a foo/.
, no foo
; ciò fa la differenza quando si foo
tratta di un collegamento simbolico a una directory: foo
indica il collegamento simbolico, foo/
indica la directory di destinazione. In tal caso, il nome di base di un percorso con una barra finale è vantaggiosamente .
e il percorso può essere il suo nome dir.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
Il metodo rapido e affidabile è usare zsh con i suoi modificatori di cronologia (questa prima striscia di barre, come le utility):
dir=$filename:h base=$filename:t
¹ A meno che non si utilizzino shell pre-POSIX come Solaris 10 e precedenti /bin/sh
(che mancavano delle funzionalità di manipolazione delle stringhe di espansione dei parametri su macchine ancora in produzione - ma c'è sempre una shell POSIX chiamata sh
nell'installazione, solo che /usr/xpg4/bin/sh
non lo è /bin/sh
).
² Ad esempio: inviare un file chiamato foo
a un servizio di caricamento file che non protegge da questo, quindi eliminarlo e causare foo
invece l' eliminazione
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Stavo leggendo attentamente e non ho notato che hai menzionato degli svantaggi.