1

Is there a non-recursive readlink -f / realpath alternative, that outputs absolute symlink destinations? A more POSIX compatible solution is welcome as well.

I have a function block in my shell script as follows:

set FILES
# addFile adds a file to the FILES list. If file is a symlink,
# it will also find and add the destination.
addFile() {
    file="$1"
    # Check if file is already included in list
    echo "$FILES" | grep -q "$file" && return
    FILES="$FILES $file"
    if [ -L $file ]; then
        addFile $(realpath $file)
        #addFile $(readlink $file)
    fi
}

I'm using this function in a script which is collecting binary and library files from around the file system. But I foresee a problem in this code:

  1. My first try I used readlink, but this returned symlink resolution paths relative to the $file, thus any actions performed on those paths fail. (Now commented out, but still in code) (See minor edit below)
  2. After that, I used realpath instead, as suggested by man readlink. It almost works.

Now, the actions that I want to perform can work. However, in the final result I'm copying over all the binaries, libraries and symlinks in an initramfs. If there are any recursive symlinks, only the top level and the destination will be included, not the intermediate ones. Thus, breaking the symlink.

I've created a fiddle to demonstrate the issue. link-to-link1 points to link1, which points to file1. In the end result link1 is missing and link-to-link1 is broken.

Edit; clarify the issue

When running the above function on the following set of symlinks:

# Contents of ~/test/src
file0
file1
link0 -> file0
link0.1 -> file0
link1 -> file1
link-to-link1 -> link1

With:

copy="link-to-link1 link0 link0.1"
for sf in $copy; do
    addFile ~/test/src/$sf
done;
cp -av $FILES ~/test/dst

You will find that only the 3 links from $copy and 2 files are copied. However, intermediate link link1 is missing and link-to-link1 is broken.

I would like the script to find link1 as well. Keeping in mind that in the environment where this script is run, the location of files and symlinks have to be absolute.

Minor edit

Add another fiddle to demonstrate failing of relative paths, using readlink. Only the symlinks are copied and the destinations are missing.

4
  • Can you give an example and the output you want for that in the question? As an aside: consider using an associative array to store the list of files, since you're using bash.
    – muru
    Commented May 25, 2018 at 7:05
  • @muru, Did you check the linked fiddle? I attached that as a longer example. If that on'e not clear enough, I'll try to make some adjustments.
    – Tim
    Commented May 25, 2018 at 7:07
  • I'm looking at it, but it's a) better for the post to be self-contained and b) we shouldn't have to parse whatever you did to create the state in addition to whatever you're going to do with that state. So it's better if you can describe the input and the output you want in the question.
    – muru
    Commented May 25, 2018 at 7:08
  • Ok, will try to clarify the question
    – Tim
    Commented May 25, 2018 at 7:10

1 Answer 1

2

If I understand right, for the example given, you want link1 for the file link-to-link1. That you can get using GNU find's -printf and %l:

$ find . -type l -printf '%p -> %l\n'
./link0.1 -> file0
./link1 -> file1
./link0 -> file0
./link-to-link1 -> link1

Since the absolute path of link1 is needed as the output for /path/to/link-to-link1, maybe cd to the directory containing link-to-link1 and run readlink/realpath in succession:

(
cd "$(dirname "$file")"
realpath -s "$(readlink "$file")"
)

realpath -s does not resolve symbolic links, but will print the absolute path.

For example:

~ file=/tmp/foo/link-to-link1
~ (cd "$(dirname "$file")"; realpath -s "$(readlink "$file")")
/tmp/foo/link1

This will also work for links to absolute paths:

~ file=/tmp/foo/link2
~ ln -sfv /tmp/foo/link1 "$file"
'/tmp/foo/link2' -> '/tmp/foo/link1'
~ (cd "$(dirname "$file")"; realpath -s "$(readlink "$file")")
/tmp/foo/link1
2
  • find's printf and %l is giving a relative link, just like readlink. The problem being, if the links are not in the $PWD, addFile() will try to add a non-existing file. Hence, I resorted to realpath for absolute names, but that one's recursive and missing intermediate links.
    – Tim
    Commented May 25, 2018 at 7:38
  • @Tim check the update
    – muru
    Commented May 25, 2018 at 8:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .