Construindo caminhos de maneira robusta

16

Digamos que eu tenha várias variáveis ​​em um script de shell (por exemplo, no zsh):

FOLDER_1, FOLDER_2, etc.

Essas variáveis ​​se referem a pastas descendentes de /. Por exemplo, se eu tiver um caminho/home/me/stuff/items

as variáveis ​​seriam:

FOLDER_1='home'
FOLDER_2='me'
FOLDER_3='stuff'

Agora, diga que eu quero construir de volta o caminho correspondente concatenando as variáveis. Uma maneira possível é construir o caminho da seguinte maneira:

PATH=$FOLDER_1/$FOLDER_2/$FOLDER_3/

No entanto, diga que algumas das variáveis FOLDER_ivêm com barras à direita, enquanto outras não (e não sabemos quais), por exemplo

FOLDER_1='home'
FOLDER_2='stuff/'
FOLDER_3='items'

Minha pergunta é: como eu poderia construir o caminho com robustez? (por exemplo, evitando barras duplas e adicionando-as onde elas precisam estar).

Eu pensei que uma maneira de fazer isso é adicionar /sempre os pares de variáveis ​​e excluir quaisquer duplicatas com sed, mas não consigo fazê-lo funcionar (não tenho certeza se estou lidando /corretamente com isso sed).

Além disso, estou reinventando a roda ? (ou seja, já existe algum built-in que faça isso?).

Por fim, se as variáveis ​​estiverem em uma matriz , por exemplo FOLDERS, seria possível fazer isso sem fazer loop? (ou, alternativamente, fazendo um loop, mas sem saber quantas FOLDERSexistem na matriz).

Amelio Vazquez-Reina
fonte

Respostas:

12

Você pode usar printfcom uma matriz:

parts=("$FOLDER_1" "$FOLDER_2" "$FOLDER_3");
printf '/%s' "${parts[@]%/}"
# Use "printf -v path" to save it into a variable called "path" instead of printing it

O %operador apara as seqüências finais, neste caso /. Ao aplicá-lo parts[@], apara cada membro da matriz separadamente.

A chave para entender esse printftruque é este pedaço de man 1 printf: "A seqüência de formato é reutilizada quantas vezes for necessário para satisfazer os argumentos".

janmoesen
fonte
O %método único funciona para a barra final mencionada na queston. Para atender quando há várias barras, ${parts[@]%%/*}funciona. Aqui está um link para um pouco mais de informação sobre o problema da barra: O que as barras duplas significam no caminho UNIX? É o 'cd dir / subdir // válido ...'
Peter.O
2
@fered: /*nesse caso, não significa zero ou mais barras , mas uma barra seguida por qualquer número de caracteres. Isso significa que, se o caminho começar com uma barra, o resultado será a string vazia!
L0b0 25/10
Eu havia considerado, como no exemplo, que haveria apenas barras à direita. No entanto, para se adaptar à possibilidade de uma barra inicial (ES), da festa de extgloblata (com regex) pelo utilizada .. shopt -s extglob; ${parts[@]%%/+(/)}...
Peter.O
15

A resposta simples é parar de se preocupar e amar várias barras. Várias barras têm o mesmo efeito que uma única barra (exceto que um caminho que começa com //tem um significado diferente em alguns sistemas; as camadas de emulação unix no Windows são as únicas que posso citar). Isso ocorre por design, e ser capaz de montar nomes de arquivos sem precisar se preocupar com múltiplas barras foi uma parte importante dessa decisão de design.

Para unir elementos da matriz, no zsh, você pode usar o j sinalizador de expansão de parâmetro .

dir=${(j:/:)FOLDERS}

Você pode esmagar as barras duplicadas enquanto faz isso, mas isso é puramente cosmético.

setopt extended_glob
dir=${${(j:/:)FOLDERS}//\/\/##/\/}

No ksh e no bash, você pode ingressar em uma matriz usando o primeiro caractere de $IFScomo um separador. Você pode espremer as barras duplicadas. No bash, você precisará fazer shopt -s extglobpara ativar os globos ksh na última linha do snippet abaixo.

IFS=/
dir="${FOLDERS[*]}"
unset IFS
dir=${dir//\/+(\/)//}
Gilles 'SO- parar de ser mau'
fonte
3

Como sednão está funcionando para você? Tente sed 's|/\+|/|g'depois da concatenação ou sed 's|/||g'antes.

Kevin
fonte
1

bash 4introduziu globbing estendido, que permite a correspondência de expressões regulares na substituição de parâmetros ${var....}... É desativado por padrão. Para habilitá-lo para o seu script, basta definir a opção shell extglob ...

Supondo que $ dir ==/home/me/////////stuff//items

shopt -s extglob; dir="${dir//+(\/)//}"

Valor resultante de $ dir

/home/me/stuff/items  

Aqui estão alguns exemplos com o que fazer e o que não fazer - Bash Extended Globbing

Peter.O
fonte