Não tenho coragem de fazer tudo de novo, mas escrevi isso em resposta ao Commandline Find Sed Exec . Nesse caso, o solicitante queria saber como mover uma árvore inteira, possivelmente excluindo um ou dois diretórios, e renomear todos os arquivos e diretórios contendo a string "OLD" para conter "NEW" .
Além de descrever o como com detalhamento meticuloso a seguir, esse método também pode ser o único que incorpora a depuração embutida. Basicamente, ele não faz nada conforme está escrito, exceto compilar e salvar em uma variável todos os comandos que acredita que deva fazer para executar o trabalho solicitado.
Também evita explicitamente loops tanto quanto possível. Além da sed
busca recursiva por mais de uma correspondência do padrão, não há outra recursão até onde eu sei.
E, por último, isso é totalmente null
delimitado - não tropeça em nenhum caractere em nenhum nome de arquivo, exceto no null
. Eu não acho que você deveria ter isso.
A propósito, isso é MUITO rápido. Veja:
% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" | tail -n 2 )
<actual process time used:>
0.06s user 0.03s system 106% cpu 0.090 total
<output from wc:>
Lines Words Bytes
115 362 20691 -
<output from tail:>
mv .config/replacement_word-chrome-beta/Default/.../googlestars \
.config/replacement_word-chrome-beta/Default/.../replacement_wordstars
NOTA:function
Provavelmente, o item acima exigirá GNU
versões de sed
e find
para lidar adequadamente com as chamadas find printf
e sed -z -e
e :;recursive regex test;t
. Se eles não estiverem disponíveis para você, a funcionalidade provavelmente pode ser duplicada com alguns pequenos ajustes.
Isso deve fazer tudo o que você queria do início ao fim, com muito pouco barulho. Eu fiz fork
com sed
, mas eu também estava praticando algumas sed
técnicas de ramificação recursiva é por isso que estou aqui. É como conseguir um corte de cabelo com desconto em uma escola de barbeiro, eu acho. Este é o fluxo de trabalho:
rm -rf ${UNNECESSARY}
- Eu deixei intencionalmente de fora qualquer chamada funcional que pudesse excluir ou destruir dados de qualquer tipo. Você mencionou que
./app
pode ser indesejado. Exclua-o ou mova-o para outro lugar com antecedência ou, alternativamente, você pode criar uma \( -path PATTERN -exec rm -rf \{\} \)
rotina find
para fazer isso de forma programática, mas isso é todo seu.
_mvnfind "${@}"
- Declare seus argumentos e chame a função de trabalho.
${sh_io}
é especialmente importante porque salva o retorno da função. ${sed_sep}
vem em segundo lugar; esta é uma string arbitrária usada para fazer referência sed
à recursão da função. Se ${sed_sep}
for definido com um valor que poderia ser potencialmente encontrado em qualquer um dos seus nomes de caminho ou arquivo agidos ... bem, não deixe assim.
mv -n $1 $2
- A árvore inteira é movida desde o início. Isso vai economizar muita dor de cabeça; acredite em mim. O resto do que você deseja fazer - a renomeação - é simplesmente uma questão de metadados do sistema de arquivos. Se você estiver, por exemplo, movendo isso de uma unidade para outra, ou através dos limites do sistema de arquivos de qualquer tipo, é melhor fazer isso de uma vez com um comando. Também é mais seguro. Observe a
-noclobber
opção definida para mv
; conforme está escrito, esta função não colocará ${SRC_DIR}
onde ${TGT_DIR}
já existe um.
read -R SED <<HEREDOC
- Eu localizei todos os comandos do sed aqui para evitar problemas de escape e os li em uma variável para alimentar o sed abaixo. Explicação abaixo.
find . -name ${OLD} -printf
- Começamos o
find
processo. Com find
, procuramos apenas por qualquer coisa que precise ser renomeada porque já fizemos todas as mv
operações local a local com o primeiro comando da função. Em vez de realizar qualquer ação direta com find
, como uma exec
chamada, por exemplo, nós a usamos para construir a linha de comando dinamicamente com -printf
.
%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
- Depois de
find
localizar os arquivos de que precisamos, ele constrói e imprime diretamente (a maior parte ) do comando que precisaremos para processar sua renomeação. O %dir-depth
anexo no início de cada linha ajudará a garantir que não estamos tentando renomear um arquivo ou diretório na árvore com um objeto pai que ainda não foi renomeado. find
usa todos os tipos de técnicas de otimização para percorrer a árvore do sistema de arquivos e não é certo que retornará os dados de que precisamos em uma ordem segura para operações. É por isso que a seguir ...
sort -general-numerical -zero-delimited
- Classificamos todas
find
as saídas de com base em de %directory-depth
modo que os caminhos mais próximos em relação a $ {SRC} sejam trabalhados primeiro. Isso evita possíveis erros envolvendo mv
arquivos em locais inexistentes e minimiza a necessidade de loop recursivo. ( na verdade, você pode ter dificuldade em encontrar um loop )
sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
- Acho que este é o único loop em todo o script, e ele apenas faz um loop sobre o segundo
%Path
impresso para cada string no caso de conter mais de um valor $ {OLD} que possa precisar ser substituído. Todas as outras soluções que imaginei envolviam um segundo sed
processo e, embora um loop curto possa não ser desejável, certamente é melhor do que gerar e bifurcar um processo inteiro.
- Então, basicamente o que
sed
faz aqui é pesquisar $ {sed_sep}, então, tendo encontrado, salva-o e todos os caracteres que encontra até encontrar $ {OLD}, que então substitui por $ {NEW}. Ele então volta para $ {sed_sep} e procura novamente por $ {OLD}, caso ocorra mais de uma vez na string. Se não for encontrado, ele imprime a string modificada stdout
(a qual captura novamente em seguida) e termina o loop.
- Isso evita ter que analisar a string inteira e garante que a primeira metade da
mv
string de comando, que precisa incluir $ {OLD}, é claro, inclua-a e a segunda metade seja alterada quantas vezes forem necessárias para limpar o $ {OLD} nome do mv
caminho de destino.
sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
- As duas
-exec
ligações aqui acontecem sem um segundo fork
. No primeiro, como vimos, que modificar o mv
comando como fornecido por find
's -printf
comando função como necessário alterar corretamente todas as referências de $ {OLD} para o $ {NEW}, mas, a fim de fazê-lo, tivemos que usar algum pontos de referência arbitrários que não devem ser incluídos na saída final. Assim, depois de sed
terminar tudo o que precisa fazer, instruímos para limpar seus pontos de referência do buffer de retenção antes de passá-lo adiante.
E AGORA ESTAMOS DE VOLTA
read
receberá um comando parecido com este:
% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
Vai read
-lo em ${msg}
como ${sh_io}
que pode ser examinado no exterior vontade da função.
Legal.
-Mike