Escape unusual characters on filenames with `find . -printf "%p \n"`
Usually you'd want to use find -exec
to run a command for all file names, or find -print0
to pipe the names to some command that can read entries separated by nul bytes (like xargs -0
).
If you really want to have quoted strings, Bash has a couple of options to do that:
$ 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
This does require an extra invocation of the shell, but handles multiple file names with one exec.
Regarding saving the permission bits (not ACL's though), you could do something like this (in GNU find):
find -printf "%#m:%p\0" > files-and-modes
That would output entries with the permissions, a colon, the filename, and a nul byte, like: 0644:name with spaces\0
. It will not escape anything, but instead will print the file names as-is (unless the output goes to a terminal, in which case at least newlines will be mangled.)
You can read the result with a Perl script:
perl -0 -ne '($m, $f) = split/:/, $_, 2; chmod oct($m), $f; ' < files-and-modes
Or barely in Bash, see comments:
while IFS=: read -r -d '' mode file ; do
# do something useful
printf "<%s> <%s>\n" "$mode" "$file"
chmod "$mode" "$file"
done < files-and-modes
As far as I tested, that works with newlines, quotes, spaces, and colons. Note that we need to use something other than whitespace as the separator, as setting IFS=" "
would remove trailing spaces if any names contain them.
With zsh
, you could do:
print -r -- ./**/*(.D:q)
.
being the equivalent of -type f
, D
being to include hidden files like find
would, and :q
for quoting (using zsh
-style quoting, I can't tell if that's the kind of quoting you're expecting).
You can get different styles of quoting with:
$ print -r -- ./**/*(.D:q)
./$'\200' ./a\ b ./é ./\"foo\" ./It\'s\ bad ./$'\n'
$ files=(./**/*(.D))
$ print -r -- ${(q)files}
./$'\200' ./$'\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}
$'./\200' $'./\n' $'./a b' $'./é' $'./"foo"' $'./It\'s bad'
(�
being a placeholder displayed by my terminal emulator for that non-printable \200 byte).
Here, if you want to be able to store the permissions in such a way that can be restored, it's just a matter of:
find . -type f -printf '%m\0%p\0' > saved-permissions
To be restored (assuming GNU xargs
) with:
xargs -r0n2 -a saved-permissions chmod
That would however run one chmod
invocation per file, which would be terribly inneficient. You may want to use a shell where chmod
is builtin like zsh
again after zmodload zsh/files
:
zmodload zsh/files
while IFS= read -rd '' mode && IFS= read -rd '' file; do
chmod $mode $file
done < saved-permissions