Como melhorar este script de shell bash para transformar hardlinks em links simbólicos?

0

Este script de shell é basicamente o trabalho de outras pessoas . Ele passou por várias iterações, e eu o ajustei levemente enquanto tentava entender completamente como ele funciona. Acho que entendi agora, mas não tenho confiança para alterá-lo por conta própria e me arrisco a perder dados quando executo a versão alterada. Então, eu gostaria de receber algumas orientações de especialistas sobre como melhorar esse script.

As mudanças que estou procurando são:

  1. torne isso ainda mais robusto para qualquer nome de arquivo estranho, se possível. Atualmente lida com espaços em nomes de arquivos, mas não em novas linhas. Eu posso viver com isso (porque eu tento encontrar qualquer nome de arquivo com novas linhas e me livrar deles).
  2. torna mais inteligente sobre qual arquivo é retido como o conteúdo real do inode e quais arquivos se tornam links sym. Eu gostaria de poder optar por manter o arquivo que é a) o caminho mais curto, b) o caminho mais longo ou c) tem o nome do arquivo com mais caracteres alfa (que provavelmente será o nome mais descritivo).
  3. permite que ele leia os diretórios para processar a partir de parâmetros passados ou de um arquivo.
  4. opcionalmente, escreva um longo período de todas as alterações e / ou todos os arquivos não processados.

De todos esses itens, # 2 é o mais importante para mim agora. Eu preciso processar alguns arquivos com ele e preciso melhorar a maneira como ele escolhe quais arquivos serão transformados em links simbólicos. (Eu tentei usar coisas como a opção de encontrar -depth sem sucesso.)

Este é o script atual:

#!/bin/bash

# clean up known problematic files first.
## find /home -type f -wholename '*Icon*
## *' -exec rm '{}' \;

# Configure script environment
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
set -o nounset
dir='/SOME/PATH/HERE/'

# For each path which has multiple links
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# (except ones containing newline)
last_inode=
while IFS= read -r path_info
do
   #echo "DEBUG: path_info: '$path_info'"
   inode=${path_info%%:*}
   path=${path_info#*:}
   if [[ $last_inode != $inode ]]; then
       last_inode=$inode
       path_to_keep=$path
   else
       printf "ln -s\t'$path_to_keep'\t'$path'\n"
       rm "$path"
       ln -s "$path_to_keep" "$path"
   fi
done < <( find "$dir" -type f -links +1 ! -wholename '*
*' -printf '%i:%p\n' | sort --field-separator=: )

# Warn about any excluded files
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
buf=$( find "$dir" -type f -links +1 -path '*
*' )
if [[ $buf != '' ]]; then
    echo 'Some files not processed because their paths contained newline(s):'$'\n'"$buf"
fi

exit 0
    
por MountainX 29.03.2012 / 07:09

1 resposta

2

1.

Uma mudança simples para não morrer nos nomes de arquivo que começam em - é adicionar -- (significa "agora todas as opções foram dadas, apenas argumentos posicionais restantes") antes dos argumentos do nome do arquivo começarem, por exemplo

rm -- "$path"
ln -s -- "$path_to_keep" "$path"

e assim por diante.

2.

Para contar caracteres alfa ("alfanuméricos" é provavelmente o que você realmente quer) em um nome de arquivo que você poderia fazer

numberofalnum=$(printf -- "$path" | tr -cd [:alnum:] | wc -m)

Para contar a profundidade do caminho, você pode tentar apenas contar as ocorrências de '/' no nome do arquivo. Uma ressalva poderia ser que /home///daniel é equivalente a /home/daniel , mas find não produzirá barras múltiplas desnecessárias, então tudo ficará bem.

depth=$(printf -- "$path" | tr -cd / | wc -m)

Também é possível recolher várias barras executando tr -s / após printf . Combinar -s , -c e -d desta forma em uma única invocação não é realmente possível, parece.

Nesse caso, como find já é usado dessa maneira no script, apenas adicionar um campo : separado na -printf output com %d imprimirá a profundidade diretamente, conforme observado abaixo no comentário .

3a.

Para ler diretórios como argumentos na linha de comando, veja este snippet mínimo:

#!/bin/sh
i=0
while [ $# -ne 0 ]; do
    printf -- 'Argument %d: %s\n' "${i}" "${1}"
    i=$((i+1))
    shift
done

( $i é apenas um contador para mostrar o que está acontecendo)

Se você envolver sua lógica em um loop while, poderá acessar o primeiro argumento como ${1} , depois usar shift , que abre o primeiro item da lista de argumentos e, em seguida, iterar novamente e agora ${1} é o segundo argumento originalmente. Faça isso enquanto a contagem de argumentos $# não for 0.

3b.

Para ler os argumentos de um arquivo, coloque-o como

#!/bin/sh
i=1
while read line; do
    printf -- 'Argument %d: %s\n' "${i}" "${line}"
    i=$((i+1))
    shift
done < "${1}"

Dica: em vez de apenas aumentar o recuo e envolver toda a lógica do arquivo dessa maneira, crie funções da lógica atual e chame-as no final do script. Isso permitirá que você escolha facilmente entre dar diretórios como argumentos ou lê-los a partir de um arquivo sem duplicar o código em seu script.

4.

Adicionar

printf 'My descriptive log message for path %s\n' "${path}" >> "${logfile}"

nos blocos lógicos onde você decidiu agir ou não. Defina $logfile antes para um caminho de log desejado.

    
por 29.03.2012 / 09:20