Ambos têm suas peculiaridades, infelizmente.
Ambos são exigidos pelo POSIX, portanto, a diferença entre eles não é uma preocupação de portabilidade¹.
A maneira simples de usar os utilitários é
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Observe as aspas duplas em torno das substituições de variáveis, como sempre, e também o --
após o comando, caso o nome do arquivo comece com um traço (caso contrário, os comandos interpretariam o nome do arquivo como uma opção). Isso ainda falha em um caso extremo, o que é raro, mas pode ser forçado por um usuário mal-intencionado²: a substituição de comandos remove as novas linhas finais. Portanto, se um nome de arquivo for chamado foo/bar
, base
ele será definido como em bar
vez de bar
. Uma solução alternativa é adicionar um caractere não-nova linha e removê-lo após a substituição do comando:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Com a substituição de parâmetro, você não encontra casos extremos relacionados à expansão de caracteres estranhos, mas há várias dificuldades com o caractere de barra. Uma coisa que não é um caso de extrema importância é que a computação da parte do diretório requer código diferente para o caso em que não existe /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
O caso de borda é quando há uma barra final (incluindo o caso do diretório raiz, que é tudo barra). Os comandos basename
e dirname
retiram as barras finais antes que eles façam seu trabalho. Não há como remover as barras finais de uma só vez, se você seguir as construções do POSIX, mas poderá fazê-lo em duas etapas. Você precisa cuidar do caso quando a entrada consistir em nada além de barras.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Se você souber que não está em um caso de borda (por exemplo, um find
resultado que não seja o ponto inicial sempre contém uma parte do diretório e não tem rastro /
), a manipulação da cadeia de expansão de parâmetros é simples. Se você precisar lidar com todos os casos extremos, os utilitários serão mais fáceis de usar (mas mais lentos).
Às vezes, você pode querer tratar foo/
como foo/.
, em vez de como foo
. Se você está atuando em uma entrada de diretório, foo/
é suposto ser equivalente a foo/.
, não foo
; isso faz diferença quando foo
existe um link simbólico para um diretório: foo
significa o link simbólico, foo/
significa o diretório de destino. Nesse caso, o nome da base de um caminho com uma barra final é vantajoso .
e o caminho pode ser seu próprio nome de diretório.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
O método rápido e confiável é usar o zsh com seus modificadores de histórico (este primeiro remove barras finais, como os utilitários):
dir=$filename:h base=$filename:t
¹ A menos que você esteja usando shells pré-POSIX como Solaris 10 e mais antigos /bin/sh
(que não possuíam recursos de manipulação de cadeia de expansão de parâmetros em máquinas ainda em produção - mas sempre há um shell POSIX chamado sh
na instalação, só que /usr/xpg4/bin/sh
não /bin/sh
).
² Por exemplo: envie um arquivo chamado foo
para um serviço de upload de arquivo que não protege contra isso, exclua-o e faça foo
com que seja excluído
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Eu estava lendo atentamente e não notei que você mencionava quaisquer desvantagens.