bash: redirect output of "find -exec" into a file without newlines; -- temporarily removing symlinks when sharing directory in VirtualBox

Viewed 52

Sometimes, I use VirtualBox with a Windows guest system under a Linux Mint host system, with a shared directory ~/_D corresponding to my D:\-drive in Windows. However, there are many symbolic links to directories below ~/_D, some of which are even circular, which causes some Windows processes not to work as I need.

Therefore I am working on a bash script that temporarily removes all symlinks below ~/_D and at the same time creates another script that can restore those symlinks later when I am done with my work on Windows.

However, I am struggling with writing the output of the find ... -exec readlink {} \; command into a file without newlines. My script currently goes like this:

#! /bin/bash

cd ~/_D

echo "# restores all symlinks deleted by ~/bin/symlink.remove" > symlinks.restore
echo >> symlinks.restore

find . -type l -exec readlink {} \; -print -exec echo "" \; -exec rm {} \; >> symlinks.restore

echo >> symlinks.restore
echo "# run emacs command sd-restore-symlinks on this file" >> symlinks.restore

chmod u+x symlinks.restore

echo to restore symlinks, run emacs command sd-restore-symlinks on ~/_D/symlinks.restore
echo then run ~/_D/symlinks.restore

Instead of awkwardly using an emacs search-and-replace-function just for rearranging the output for each symlink from two lines, separated by an empty line, into one line, resulting in lines such as ln -s "../.Events.OLD" "./Events/Events.OLD" (mind the quotes to avoid havoc with possible spaces in filenames), I would love to generate the output file symlinks.restore directly from my bash script.

However, using hacks like: find -type l -exec echo -n $(readlink {}) \; does not work, probably because{} is not accessible as alias for the current file within $(), also not when quoted \{\}.

I guess that some smart combination of -printf or -print0 actions of find, and/or perhaps sed, can do the trick, but this goes beyond what I can achieve.

Also, perhaps there are already tools to temporarily remove and then restore all symlinks below a certain directory?

Any ideas?

3 Answers

With -printf something like.

find . -type l -printf 'ln -s "%p" "%l"\n' > symlinks.restore

It has limitations and it is not bullet proof, which was mentioned by M. Nejat Aydin

You may use this find command to create restore script:

find . -type l -exec bash -c '
    for symlnk; do
        printf "ln -s %q %q\n" "$(readlink "$symlnk")" "$symlnk"
    done' _ {} + > symlinks.restore

The restore script (symlinks.restore) must be run (or sourced) from the same directory as the find command is run.

Thanks to both, M. Nejat Aydin and Jetchisel.

The latter solution worked best for me, with some adjustments (quotes, changed order of parameters %p and %l, and added rm command).

My script now reads:

#! /bin/bash

cd ~/_D

printf "#! /bin/bash\n\n# restores all symlinks deleted by ~/bin/symlink.remove\n# has to be executed in _D\n\n" > symlinks.restore

find . -type l -printf 'ln -s "%l" "%p"\n' -exec rm {} \; >> symlinks.restore

chmod u+x symlinks.restore

This works fine, even with spaces in filenames.

Differently from my earlier comments, the other solution also works without producing literal backslashes in the link targets; the relevant command I now successfully used is

find . -type l -exec bash -c '
    for symlnk; do
        printf "ln -s \"%q\" \"%q\"\n" "$(readlink "$symlnk")" "$symlnk"
    rm $symlnk
    done' _ {} + >> symlinks.restore

Thanks again to all who responded.

Related