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.foo/
comofoo
, não comofoo/.
, o que não é consistente com os utilitários compatíveis com POSIX./
se precisar.find
resultado, que sempre contém uma parte do diretório e não tem/
" "Não é verdade,find ./
será exibido./
como o primeiro resultado.Ambos estão no POSIX, portanto, a portabilidade "não deve" ser uma preocupação. Presume-se que as substituições de shell sejam executadas mais rapidamente.
No entanto - depende do que você quer dizer com portátil. Alguns sistemas antigos (não necessários) não implementavam esses recursos em seus dispositivos
/bin/sh
(Solaris 10 e anteriores), enquanto, por outro lado, há algum tempo, os desenvolvedores foram avisados de quedirname
não eram tão portáteis quantobasename
.Para referência:
dirname - retorna a parte do diretório de um nome de caminho (POSIX)
página de manual sh no Solaris 10 (Oracle)
A página de manual não menciona
##
ou%/
.Ao considerar a portabilidade, eu precisaria levar em consideração todos os sistemas em que mantenho programas. Nem todos são POSIX, portanto, existem vantagens e desvantagens. Suas trocas podem ser diferentes.
fonte
Há também:
Coisas estranhas como essa acontecem porque há muita interpretação e análise e o restante precisa acontecer quando dois processos falam. As substituições de comando removerão as novas linhas à direita. E NULs (embora isso obviamente não seja relevante aqui) .
basename
edirname
também tirará novas linhas de qualquer maneira, porque de que outra forma você fala com elas? Eu sei, rastrear novas linhas em um nome de arquivo é uma espécie de anátema, mas você nunca sabe. E não faz sentido seguir o caminho possivelmente defeituoso quando você poderia fazer o contrário.Ainda ...
${pathname##*/} != basename
e da mesma forma${pathname%/*} != dirname
. Esses comandos são especificados para executar uma sequência de etapas na maior parte bem definida para chegar aos resultados especificados.A especificação está abaixo, mas primeiro aqui está uma versão terser:
Isso é totalmente compatível com POSIX de
basename
maneira simplessh
. Não é difícil de fazer. Mesclei algumas ramificações que uso abaixo porque poderia, sem afetar os resultados.Aqui está a especificação:
... talvez os comentários sejam perturbadores ...
fonte
[!/]
antes, é assim[^/]
? Mas o seu comentário ao lado que não parece corresponder-lo ....basename
é um conjunto de instruções sobre como fazê-lo com seu shell. Mas[!charclass]
a maneira portátil de fazer isso com os globs[^class]
é para o regex - e as conchas não são especificadas para o regex. Sobre a correspondência docase
filtro de comentários ... , por isso, se eu corresponder a uma string que contém uma barra final/
e um!/
se o próximo padrão de caso abaixo corresponder a qualquer barra final,/
elas podem ser apenas todas as barras. E um abaixo que não pode ter nenhum /Você pode obter um impulso do processo
basename
edirname
(não entendo por que eles não são internos - se não são candidatos, não sei o que é) -, mas a implementação precisa lidar com coisas como:^ De nome base (3)
e outros casos extremos.
Eu tenho usado:
(Minha mais recente implementação do GNU
basename
edirname
adiciona algumas opções especiais de linha de comando para coisas como lidar com vários argumentos ou remoção de sufixos, mas isso é super fácil de adicionar no shell.)Também não é tão difícil transformá-los em
bash
componentes internos (usando a implementação do sistema subjacente), mas a função acima não precisa ser compilada, e eles também fornecem algum impulso.fonte
x//
corretamente, mas eu as corrigi antes de responder. Espero que seja isso.dirname a///b//c//d////e
rendimentosa///b//c//d///
.