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 sedbusca 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 nulldelimitado - 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á GNUversões de sede findpara lidar adequadamente com as chamadas find printfe sed -z -ee :;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 forkcom sed, mas eu também estava praticando algumas sedté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
./apppode 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 findpara 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
-noclobberopçã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
findprocesso. Com find, procuramos apenas por qualquer coisa que precise ser renomeada porque já fizemos todas as mvoperações local a local com o primeiro comando da função. Em vez de realizar qualquer ação direta com find, como uma execchamada, 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
findlocalizar 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-depthanexo 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. findusa 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
findas saídas de com base em de %directory-depthmodo que os caminhos mais próximos em relação a $ {SRC} sejam trabalhados primeiro. Isso evita possíveis erros envolvendo mvarquivos 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
%Pathimpresso 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 sedprocesso 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
sedfaz 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
mvstring 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 mvcaminho de destino.
sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
- As duas
-execligações aqui acontecem sem um segundo fork. No primeiro, como vimos, que modificar o mvcomando como fornecido por find's -printfcomando 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 sedterminar 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