find - exec e empilhando vários comandos

1

Estou criando um script para cópia / soma de verificação de arquivos .... rodando o Mac OS X / FreeBSD com a possibilidade de migrar para o CentOS, Debian ou OpenBSD

Mais sobre o script:

  1. verifique se o caminho de origem contém arquivos / subdiretórios
  2. criar soma de verificação de arquivo (s) para cada dir / subdir
  3. tar / compress no caminho de destino
  4. verificar a integridade do arquivo no caminho de destino

É claro que é paranóico porque as verificações de integridade de arquivos são feitas no nível HW / HDD e podem ser verificadas facilmente pelo S.M.A.R.T. mas dez anos depois não consigo verificar a integridade do original. Checksum é criado no cartão CF / XD e é original ... você pode copiar o quanto quiser e nunca se preocupar com os chamados rotten-bits, erros HW e assim por diante.

É claro que rsync pode ser usado também, mas eu não gosto de idéia de checksums MD5 / SHA1 obsoletos com possíveis colisões. Demora horas de trabalho, "sorte" e suor para estar no lugar certo na hora certa e tirar uma foto única ... se você perder RAW original ... ele se foi para sempre, apenas a memória permanece.

"Only paranoid survive" -- Andy Groove

Eu tenho um script de trabalho simples para o passo 1. no script:

today='date +%Y-%m-%d'
CHK='shasum -a512'
CHK_OUTPUT=($today)-checksum.txt
find . -type f ! -name  ".*" -maxdepth 1 -exec $CHK {} \; > "$CHK_OUTPUT"

Eu recebo o arquivo checksum como esperado, mas a pergunta é "Podemos melhorar?"

...cf83e1357eef47417a81a538327af927da3e  ./(2017-07-19)-checksum.txt

Eu quero me livrar do irritante ./ então codifiquei seguindo ...

find ./ -type f ! -name  ".*" -maxdepth 1 -exec bash -c '$CHK $(basename {}) > $CHK_OUTPUT' \;

infelizmente, recebo o seguinte erro

bash: ${CHK_OUTPUT}: ambiguous redirect

outra tentativa

    find ./ -type f ! -name  ".*" -maxdepth 1 -exec bash -c '$CHK $(basename {})' \; > $CHK_OUTPUT

de alguma forma funciona, mas com resultados estranhos

Eu falhei pelo UTFM & RTFM e eu não tenho ideia de como perguntar ao Google :-D

Alguém pode sugerir como fazer isso, por favor?

Atenciosamente

David

    
por David Hajes 19.07.2017 / 17:31

2 respostas

2

Como passar argumentos para um subshell em -exec

Com find do -exec , você pode passar comandos complexos usando um subshell, conforme identificado corretamente. No entanto, existem alguns problemas com sua abordagem.

Sua variável externa $CHK não será expandida, pois está entre aspas simples. O que você pode fazer para que o subshell saiba que esta variável é export antes:

$ foo=bar
$ find . -type f -exec sh -c 'echo "$foo"' \;
(returns an empty line for every file found)

$ export foo=bar
$ find . -type f -exec sh -c 'echo "$foo"' \;
(returns "bar" for every file found)

Exportar uma variável faz com que ela faça parte do seu ambiente, que as subshells podem ler. Ou você passa isso como um argumento separado para o subshell, que é o caminho a seguir aqui:

$ foo=bar
$ find . -type f -exec sh -c 'echo "$0"' "$foo" \;
(returns "bar" for every file found)

Claro, você pode continuar com $1 , $2 etc e usar {} como um argumento para seu subshell, também, para usar o nome do arquivo real. E não se esqueça de citar suas variáveis.

Seu caso específico

Você pode simplesmente reescrever seu comando em:

shasum -a512 * > "$CHK_OUTPUT"

porque shasum é inteligente o suficiente para fazer o trabalho em um comando, lendo vários arquivos, sem um loop ou find . Por padrão, * não inclui arquivos que começam com um ponto (mas você pode alterá-lo com shopt -s dotglob ), portanto, as opções find são desnecessárias, especialmente quando maxdepth é 1.

Mas vamos fingir que shasum não foi tão inteligente, então eu posso te dar mais algumas opções. Se você quiser usar find , é assim que você lida com vários argumentos:

CHK='shasum -a512'
find ./ -type f ! -name  ".*" -maxdepth 1 -exec \
sh -c '$0 "$(basename "$1")"' "$CHK" {} \; > "$CHK_OUTPUT"

Mas, mesmo assim, tudo isso também pode ser reescrito como mais legível:

today=$(date +%Y-%m-%d)
for f in *; do shasum -a512 "$f" > "($today)-checksum.txt"; done

Geralmente, é mais fácil percorrer os arquivos do que usar find , embora haja um limite de quantos arquivos você pode processar dessa forma devido à expansão do * . Em algum momento, será muito longo para você linha de comando (o limite específico depende do seu SO e shell).

E, claro, você pode fazer isso de forma recursiva com shopt -s globstar no Bash ≥ 4.0:

shopt -s globstar
for f in **/*; do …; done

Mas isso novamente é o mesmo que:

shasum -a512 **/* > "$CHK_OUTPUT"
    
por 19.07.2017 / 20:13
0

UPDATE

Infelizmente, o comando abaixo mencionado ainda cria checksum.txt vazio em subdiretórios onde não há arquivo (s).

find ./ -type f \( ! -iname ".*" ! -iname "\(????-??-??\)-checksum.txt" \) -maxdepth 1 -exec sh -c '$0 "$(basename "$1")"' "$CHK" {} \; > $CHK_OUTPUT

Eu não sei porque, mas há um kludge

# remove empty checksum.txt
    if [[ -f $CHK_OUTPUT ]] && [[ ! -s $CHK_OUTPUT ]]; then
        rm $CHK_OUTPUT
    fi

=============================================== ======

solução final ... parece funcionar até agora **

find ./ -type f \( ! -iname ".*" ! -iname "\(????-??-??\)-checksum.txt" \) -maxdepth 1 -exec sh -c '$0 "$(basename "$1")"' "$CHK" {} \; > $CHK_OUTPUT
    
por 23.07.2017 / 19:07