Encontre um arquivo e faça um symlink para pai usando find e -exec

3

Estou tentando usar find para encontrar arquivos que correspondam a um determinado padrão e, em seguida, ligar simbolicamente suas directoras pai a outro diretório, este é meu script atual (estou fazendo isso no mac para que o printf não funcione aqui ):

#!/usr/bin/env bash

PROCESS_DIR="/Users/me/Google Drive/Me"
OUTPUT_DIR="/Users/me/OfflineFolder"

# Copy directory structure and then make symlinks
rm -R "$OUTPUT_DIR";
mkdir "$OUTPUT_DIR";
cd "$PROCESS_DIR";

find . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \;

Mas parece que não está funcionando. Este script, no entanto, funciona (faz links simbólicos para todos os arquivos md no meu computador:

# Copy directory structure and then make symlinks
rm -R "$OUTPUT_DIR";
mkdir "$OUTPUT_DIR";
cd "$PROCESS_DIR";
find . -type d -exec mkdir -vp "$OUTPUT_DIR/{}" \;
find . -name *.md -exec ln -vs "$(pwd)/{}" "$OUTPUT_DIR/{}" \;
find "$OUTPUT_DIR" -type d -empty -delete

Alguma ideia de por que isso não está funcionando? Eu tentei várias abordagens diferentes (incluindo usando find $PROCESS_DIRECTORY em vez de cd $PROCESS_DIRECTORY ). Obrigado

    
por timhc22 16.06.2017 / 04:55

1 resposta

2

Você tem dois problemas, ambos relacionados à ordem em que as coisas acontecem.

find . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \; é um único comando. O shell analisa antes de executá-lo. Entre o passo em análise:

  • $(…) é uma substituição de comando, portanto, dirname {} é executado, gerando . (não há uma parte do diretório em {} ).
  • $OUTPUT_DIR é uma substituição de variável, então ela é substituída pelo seu valor.
  • *.md é um padrão glob, sendo substituído pela lista de arquivos correspondentes. Se não houver correspondências, o padrão permanece no lugar.

Isso conclui o trabalho necessário para determinar qual comando executar.

  • Se não houver arquivos correspondentes a *.md no diretório atual, o seguinte comando será executado com os argumentos fornecidos: find , . , -name , *.md , -exec , ln , -vs , . , /Users/me/OfflineFolder , ; .
  • Se *.md corresponder a bar.md e foo.md , o comando será find , . , -name , foo.md , -exec ,… (e find perderá qualquer arquivo chamado something-other-than-foo.md em subdiretórios).
  • Se o *.md corresponder a bar.md e foo.md , o comando será find , . , -name , bar.md , foo.md , -exec ,… (e find será reclamar de um erro de sintaxe).

Você deseja executar dirname nos resultados da pesquisa. Isso significa que você precisa instruir find para executar dirname . Você pode fazer isso, mas encontrar não possui nenhum mecanismo para reunir a saída de dirname e passá-la como um argumento para ln . Para isso você precisa de uma ferramenta como um shell, onde é uma substituição de comando.

Então aqui está a estratégia: tell find para invocar um shell e dizer ao shell para executar o comando envolvendo ln e dirname . Você precisa cuidar de citar. Coloque o comando shell entre aspas simples para evitar que seus caracteres especiais sejam interpretados pelo shell externo. Coloque também o padrão para -name entre aspas, de modo que seja passado para find e não expandido pela camada externa.

find . -name '*.md' -exec sh -c 'ln -vs …' \;

O próximo passo é completar o . Não use {} dentro do comando shell: isso apenas colocaria o nome do arquivo como um trecho de código shell e qualquer caractere especial seria analisado pelo shell interno. Em vez disso, passe o nome do arquivo dado por find como um argumento para o script de shell. O primeiro argumento após sh -c CODE é o nome da instância do shell ( $0 ), mas você pode usá-lo para qualquer propósito que desejar; argumento subseqüente são os parâmetros posicionais ( $1 , $2 ,…).

find . -name '*.md' -exec sh -c 'ln -vs "$(dirname "$0")" "$1"' {} "$OUTPUT_DIR" \;

Eu passei $OUTPUT_DIR como argumento para o script. Não importa aqui, porque o valor não contém caracteres especiais de shell, mas é um bom hábito de entrar, você nunca sabe quando alguém pode mudar o caminho para incluir espaços. Outra possibilidade seria passá-lo pelo meio ambiente:

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'ln -vs "$(dirname "$0")" "$OUTPUT_DIR"' {} \;

Em vez de dirname , você pode usar uma substituição textual: remova tudo após a última barra. Você não precisa se preocupar com o caso especial em que não há uma parte do diretório, pois isso não acontece com um nome de arquivo passado por find .

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'ln -vs "${0%/*}" "$OUTPUT_DIR"' {} \;

Você pode usar a forma + de -exec para acelerar um pouco as coisas. Eu passo _ as $0 e os argumentos subseqüentes são os nomes dos arquivos que o loop for itera.

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'for x; do ln -vs "${x%/*}" "$OUTPUT_DIR"; done' _ {} +
    
por 16.06.2017 / 23:27