Escape caracteres incomuns em nomes de arquivos com 'find. -printf "% p \ n" '

2

Os caminhos de arquivos provenientes deste comando find

find . -printf "%p \n"

não escape de caracteres incomuns (espaço em branco, barra invertida, aspas duplas ...).

A opção -ls imprime os caminhos com escape, mas apenas preenche a saída de ls -dils para a saída de printf .

Eu preciso de um comando altamente eficiente, portanto, executar um ls extra não ajuda, e nem a impressão de todos os caracteres extras.

Existe alguma outra maneira (elegante) de eliminar trajetos de escape com find ?

    
por Julen Larrucea 19.04.2017 / 12:24

3 respostas

3

Geralmente você deseja usar find -exec para executar um comando para todos os nomes de arquivos ou find -print0 para enviar os nomes para algum comando que possa ler entradas separadas por nul bytes (como xargs -0 ).

Se você realmente deseja ter strings entre aspas, o Bash tem algumas opções para fazer isso:

$ find -exec bash -c 'printf "%s\n" "${@@Q}"' sh {} +
'./single'\''quote'
'./space 1'
$'./new\nline'
'./double"quote'

$ find -exec bash -c 'printf "%q\n" "$@"' sh {} +
./single\'quote
./space\ 1
$'./new\nline'
./double\"quote

Isso requer uma invocação extra do shell, mas lida com vários nomes de arquivos com um exec.

Em relação a salvar os bits de permissão (embora não o ACL), você poderia fazer algo assim (no GNU find):

find -printf "%#m:%p
perl -0 -ne '($m, $f) = split/:/, $_, 2; chmod oct($m), $f; ' < files-and-modes 
" > files-and-modes

Isso produziria entradas com as permissões, dois-pontos, o nome do arquivo e um byte nulo, como: 0644:name with spacesIFS=" " . Ele não escapará de nada, mas imprimirá os nomes dos arquivos como estão (a menos que a saída vá para um terminal, caso em que pelo menos novas linhas serão desconfiguradas.)

Você pode ler o resultado com um script Perl:

while IFS=: read -r -d '' mode file ; do
    # do something useful
    printf "<%s> <%s>\n" "$mode" "$file"
    chmod "$mode" "$file"
done < files-and-modes

Ou mal no Bash, veja os comentários:

$ find -exec bash -c 'printf "%s\n" "${@@Q}"' sh {} +
'./single'\''quote'
'./space 1'
$'./new\nline'
'./double"quote'

$ find -exec bash -c 'printf "%q\n" "$@"' sh {} +
./single\'quote
./space\ 1
$'./new\nline'
./double\"quote

Tanto quanto eu testei, isso funciona com novas linhas, citações, espaços e dois-pontos . Note que precisamos usar algo diferente de espaço em branco como separador, já que a configuração %code% removeria espaços à direita se algum nome os contiver.

    
por 19.04.2017 / 12:41
2

Com zsh , você poderia fazer:

print -r -- ./**/*(.D:q)

. sendo o equivalente de -type f , D incluindo arquivos ocultos como find , e :q para aspas (usando zsh , não sei se isso é o tipo de citação que você está esperando).

Você pode obter diferentes estilos de citações com:

$ print -r -- ./**/*(.D:q)
./$'0' ./a\ b ./é ./\"foo\" ./It\'s\ bad ./$'\n'
$ files=(./**/*(.D))
$ print -r -- ${(q)files}
./$'0' ./$'\n' ./a\ b ./é ./\"foo\" ./It\'s\ bad
$ print -r -- ${(qq)files}
'./�' './
' './a b' './é' './"foo"' './It'\''s bad'
$ print -r -- ${(qqq)files}
"./�" "./
" "./a b" "./é" "./\"foo\"" "./It's bad"
$ print -r -- ${(qqqq)files}
$'./0' $'./\n' $'./a b' $'./é' $'./"foo"' $'./It\'s bad'

( sendo um espaço reservado exibido pelo meu emulador de terminal para esse byte não imprimível \ 200).

Aqui, se você quiser armazenar as permissões de tal forma que possam ser restauradas, é só uma questão de:

find . -type f -printf '%m
xargs -r0n2 -a saved-permissions chmod
%p
zmodload zsh/files
while IFS= read -rd '' mode && IFS= read -rd '' file; do
  chmod $mode $file
done < saved-permissions
' > saved-permissions

Para ser restaurado (assumindo o GNU xargs ) com:

print -r -- ./**/*(.D:q)

Isso, no entanto, executaria uma invocação de chmod por arquivo, o que seria extremamente ineficiente. Você pode querer usar um shell em que chmod esteja integrado como zsh novamente após zmodload zsh/files :

$ print -r -- ./**/*(.D:q)
./$'0' ./a\ b ./é ./\"foo\" ./It\'s\ bad ./$'\n'
$ files=(./**/*(.D))
$ print -r -- ${(q)files}
./$'0' ./$'\n' ./a\ b ./é ./\"foo\" ./It\'s\ bad
$ print -r -- ${(qq)files}
'./�' './
' './a b' './é' './"foo"' './It'\''s bad'
$ print -r -- ${(qqq)files}
"./�" "./
" "./a b" "./é" "./\"foo\"" "./It's bad"
$ print -r -- ${(qqqq)files}
$'./0' $'./\n' $'./a b' $'./é' $'./"foo"' $'./It\'s bad'
    
por 19.04.2017 / 13:06
1

Encontrei uma solução muito mais simples:

find -exec echo \'{}\' \;

Podemos usar isso para canalizar qualquer outro programa:

find -exec echo \'{}\' \; | xargs ls 

Ou podemos simplesmente usar qualquer comando em vez de echo e adicionar qualquer número de argumentos:

find -exec mv \'{}\' somewhereelse \;

Não se esqueça de adicionar antes echo ao teste.

O último NÃO FUNCIONA porque o mv não remove as aspas antes de acessar o arquivo, use o pipe:

find -exec echo mv \'{}\' somewhereelse \;| bash
    
por 13.12.2018 / 15:30