Ambos têm suas peculiaridades, infelizmente.
Ambos são requeridos pelo POSIX, então a diferença entre eles não é um problema 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 iniciais. Portanto, se um nome de arquivo for chamado de foo/bar
, então base
será definido como bar
em vez de bar
. Uma solução alternativa é adicionar um caractere não pertencente à 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âmetros, você não se depara com casos de borda relacionados à expansão de caracteres estranhos, mas há várias dificuldades com o caractere de barra. Uma coisa que não é um argumento de ponta é que calcular a parte do diretório requer um código diferente para o caso em que não há /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
O caso de borda é quando há uma barra à direita (incluindo o caso do diretório raiz, que é todas as barras). Os comandos basename
e dirname
eliminam barras finais antes de executarem o trabalho. Não há como dividir as barras finais de uma só vez se você mantiver as construções POSIX, mas você pode fazê-lo em duas etapas. Você precisa cuidar do caso quando a entrada consiste 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 acontecer de você saber que não está em um caso de borda (por exemplo, um find
result, que sempre contém uma parte do diretório e não possui /
), a manipulação da cadeia de expansão do parâmetro é simples. Se você precisa lidar com todos os casos de borda, os utilitários são mais fáceis de usar (mas mais lentos).
Às vezes, convém tratar foo/
como foo/.
em vez de foo
. Se você estiver agindo em uma entrada de diretório, foo/
deve ser equivalente a foo/.
, não foo
; isso faz diferença quando foo
é 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 de base de um caminho com uma barra final é, com vantagem, .
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 (essas primeiras faixas barras à direita, como os utilitários):
dir=$filename:h base=$filename:t
¹ A menos que você esteja usando shells pré-POSIX como Solaris 10 e /bin/sh
(que não tinham recursos de manipulação de strings de expansão de parâmetros em máquinas ainda em produção - mas há sempre um shell POSIX chamado sh
no instalação, apenas é /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 proteja contra isso, depois apague-o e faça com que foo
seja excluído