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 abar.md
efoo.md
, o comando seráfind
,.
,-name
,foo.md
,-exec
,… (efind
perderá qualquer arquivo chamadosomething-other-than-foo.md
em subdiretórios). - Se o
*.md
corresponder abar.md
efoo.md
, o comando seráfind
,.
,-name
,bar.md
,foo.md
,-exec
,… (efind
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' _ {} +