bashfindrelative-pathabsolute-valuerealpath

bash get relative path from absoulte


This line gets absolute path, i used output to pass it to rsync, but rsync wants relative path

find /www-data/ -type f -exec sh -c 'if ! lsof `readlink -f {}` > /dev/null; then echo `realpath {}`; fi' \; | tr '\n' '\0' 

No idea how to feed realpath --relative-to from above output

Full code:

cd /www-data
find ./ -type f -exec sh -c 'if ! lsof `readlink -f {}` > /dev/null; then echo `realpath {}`; fi' \; | tr '\n' '\0' | rsync -avz --from0 --files-from=- ./ /data/map/uploads/ --dry-run

Solution

  • Using tr '\n' '\000' is fundamentally broken. The reason you want to push in null-terminated strings is to disambiguate between newlines which are part of a file name, and those which aren't; but if you are replacing all newlines, you are not disambiguating anything. Perhaps see also https://mywiki.wooledge.org/BashFAQ/020

    Somewhat similarly, echo `command` is just a useless use of echo, unless you specifically want the shell to squish whitespace and expand wildcards in the output from command.

    If I'm allowed to guess slightly at what you are actually trying to ask here, try

    find ./ -type f -exec sh -c 'for f; do
        lsof "$(readlink -f "$f")" > /dev/null ||
        printf "%s\0" "$(realpath --relative-to /var/www-data "$f")"
      done' _ {} + |
    rsync -avz --from0 --files-from=- ./ /data/mapis/clientuploads/ --dry-run
    

    The crucial change is really to have find pass in the file names as arguments to sh -c '...' rather than try to replace {} smack dab in the middle of a string which may or may not require quoting.

    Using -exec ... {} + with a + at the end should improve efficiency somewhat, at the very minor cost of adding a for loop to the embedded sh script.