POSIX
Para normalizar as barras em todos os parâmetros, usarei o truque do argumento rotativo: shift $1
off, transforme-o e coloque o resultado no final da lista de parâmetros. Se você fizer isso quantas vezes houver parâmetros, você transformou todos os parâmetros e os colocou de volta em ordem.
Para a segunda parte do código, alterei sua lógica para ser menos confusa: o loop externo itera sobre os parâmetros e o loop interno itera sobre os componentes do caminho. for x; do … done
itera sobre os parâmetros posicionais, é um idioma conveniente. Eu uso uma maneira compatível com POSIX de combinar uma string com um padrão: o case
construct.
Testado com o traço 0.5.5.1, pdksh 5.2.14, bash 3.2.39, bash 4.1.5, ksh 93s +, zsh 4.3.10.
Nota: parece haver um bug no bash 4.1.5 (não no 3.2): se o padrão do caso for "${common_path%/}"/*
, um dos testes falhará.
posix_path_common () {
for tmp; do
tmp=$(printf %s. "$1" | tr -s "/")
set -- "$@" "${tmp%.}"
shift
done
common_path=$1; shift
for tmp; do
while case ${tmp%/}/ in "${common_path%/}/"*) false;; esac; do
new_common_path=${common_path%/*}
if [ "$new_common_path" = "$common_path" ]; then return 1; fi
common_path=$new_common_path
done
done
printf %s "$common_path"
}
bash, ksh
Se você está em bash (ou ksh), você pode usar arrays - não entendo por que você parece se restringir aos parâmetros posicionais. Aqui está uma versão que usa uma matriz. Eu tenho que admitir que não é particularmente mais claro que a versão POSIX, mas evita o embaralhamento n ^ 2 inicial.
Para a parte de normalização de barra, eu uso a construção ksh93 ${foo//PATTERN/REPLACEMENT}
construct para substituir todas as ocorrências de PATTERN
em $foo
por REPLACEMENT
. O padrão é +(\/)
para corresponder a uma ou mais barras; sob bash, shopt -s extglob
deve estar em vigor (equivalentemente, inicie o bash com bash -O extglob
). A construção set ${!a[@]}
configura os parâmetros posicionais para a lista de subíndices da matriz a
. Isso fornece uma maneira conveniente de iterar os elementos da matriz.
Para a segunda parte, eu tenho a mesma lógica de loop que a versão POSIX. Dessa vez, posso usar [[ … ]]
, já que todos os shells aqui direcionados suportam isso.
Testado com o bash 3.2.39, bash 4.1.5, ksh 93s +.
array_path_common () {
typeset a i tmp common_path new_common_path
a=("$@")
set ${!a[@]}
for i; do
a[$i]=${a[$i]//+(\/)//}
done
common_path=${a[$1]}; shift
for tmp; do
tmp=${a[$tmp]}
while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
new_common_path="${common_path%/*}"
if [[ $new_common_path = $common_path ]]; then return 1; fi
common_path="$new_common_path"
done
done
printf %s "$common_path"
}
zsh
Infelizmente, zsh não possui o recurso ${!array[@]}
para executar a versão do ksh93 como está. Felizmente, o zsh tem dois recursos que facilitam a primeira parte. Você pode indexar os parâmetros posicionais como se fossem a matriz @
, portanto não há necessidade de usar uma matriz intermediária. E o zsh tem uma construção da iteração de matriz : "${(@)array//PATTERN/REPLACEMENT}"
realiza a substituição do padrão em cada elemento da matriz, por sua vez, avalia a matriz de resultados (confusamente, você precisa das aspas duplas mesmo que o resultado seja várias palavras; isso é uma generalização de "$@"
). A segunda parte é essencialmente inalterada.
zsh_path_common () {
setopt local_options extended_glob
local tmp common_path new_common_path
set -- "${(@)@//\/##//}"
common_path=$1; shift
for tmp; do
while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
new_common_path="${common_path%/*}"
if [[ $new_common_path = $common_path ]]; then return 1; fi
common_path="$new_common_path"
done
done
printf %s "$common_path"
}
Casos de teste
Minhas soluções são minimamente testadas e comentadas. Eu mudei a sintaxe de seus casos de teste para analisar em shells que não têm $'…'
e relatar falhas de uma forma mais conveniente.
do_test () {
if test "$@"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}
run_tests () {
function_to_test=$1; shift
failed=0
do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
do_test "$($function_to_test / /a/b/c; echo x)" = /x
do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
do_test "$($function_to_test --/-- --; echo x)" = '--x'
do_test "$($function_to_test '' ''; echo x)" = x
do_test "$($function_to_test /foo/bar ''; echo x)" = x
do_test "$($function_to_test /foo /fo; echo x)" = x
do_test "$($function_to_test '--$'\! *@ \a\b\e\E\f\r\t\v\\"'\''
' '--$'\! *@ \a\b\e\E\f\r\t\v\\"'\''
'; echo x)" = '--$'\! *@ \a\b\e\E\f\r\t\v\\"'\''
'x
do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
do_test "$($function_to_test foo foo; echo x)" = foox
do_test "$($function_to_test /fo /foo; echo x)" = x
if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}