Linux: Find all symlinks of a given 'original' file? (reverse 'readlink')
Asked Answered
C

7

67

Consider the following command line snippet:

$ cd /tmp/
$ mkdir dirA
$ mkdir dirB
$ echo "the contents of the 'original' file" > orig.file
$ ls -la orig.file 
-rw-r--r-- 1 $USER $USER 36 2010-12-26 00:57 orig.file

# create symlinks in dirA and dirB that point to /tmp/orig.file:

$ ln -s $(pwd)/orig.file $(pwd)/dirA/
$ ln -s $(pwd)/orig.file $(pwd)/dirB/lorig.file
$ ls -la dirA/ dirB/
dirA/:
total 44
drwxr-xr-x  2 $USER $USER  4096 2010-12-26 00:57 .
drwxrwxrwt 20 root          root          36864 2010-12-26 00:57 ..
lrwxrwxrwx  1 $USER $USER    14 2010-12-26 00:57 orig.file -> /tmp/orig.file

dirB/:
total 44
drwxr-xr-x  2 $USER $USER  4096 2010-12-26 00:58 .
drwxrwxrwt 20 root          root          36864 2010-12-26 00:57 ..
lrwxrwxrwx  1 $USER $USER    14 2010-12-26 00:58 lorig.file -> /tmp/orig.file

At this point, I can use readlink to see what is the 'original' (well, I guess the usual term here is either 'target' or 'source', but those in my mind can be opposite concepts as well, so I'll just call it 'original') file of the symlinks, i.e.

$ readlink -f dirA/orig.file 
/tmp/orig.file
$ readlink -f dirB/lorig.file 
/tmp/orig.file

... However, what I'd like to know is - is there a command I could run on the 'original' file, and find all the symlinks that point to it? In other words, something like (pseudo):

$ getsymlinks /tmp/orig.file
/tmp/dirA/orig.file 
/tmp/dirB/lorig.file
Colitis answered 26/12, 2010 at 0:4 Comment(0)
D
34

I've not seen a command for this and it's not an easy task, since the target file contains zero information on what source files point to it.

This is similar to "hard" links but at least those are always on the same file system so you can do a find -inode to list them. Soft links are more problematic since they can cross file systems.

I think what you're going to have to do is basically perform an ls -al on every file in your entire hierarchy and use grep to search for -> /path/to/target/file.

For example, here's one I ran on my system (formatted for readability - those last two lines are actually on one line in the real output):

pax$ find / -exec ls -ald {} ';' 2>/dev/null | grep '\-> /usr/share/applications'
lrwxrwxrwx 1 pax pax 23 2010-06-12 14:56 /home/pax/applications_usr_share
                                         -> /usr/share/applications
Doge answered 26/12, 2010 at 0:18 Comment(8)
Hi @Doge - likewise thanks for the clear answer; +accept for the reference to 'hard links' and 'find -inode'. Cheers!Colitis
Don't forget you can have symlinks on servers where the fs is nfs mounted. So it may not be enough simply to look at the local server.Holliehollifield
This won't work with either recursive links (links to links to...) or relative links (whatever -> ../../usr/share/applications). Handling those cases is harder. Also, it spawns a new instance of ls for every file checked, meaning it'll be pretty slow.Donau
@Gordon, you're right, it won't work as-is for longer chains but it's not clear to me that was a requirement. You could make a script which would follow chains like that if you wish but, like the NFS-mounted symlinks that dietbuddha mentioned, it another level of problems. As to the efficiency, it's well known (or at least answered elsewhere on SO) that find with xargs is better for efficiency but that's not relevant to the specific question here. If this turns out to be too slow then by all means ask how to make it faster. But, if it's a one-off sort of thing, it's probably not needed.Doge
Hi @dietbuddha, @Gordon Davisson, @Doge - thanks for writing down those issues! Just wanted to restate that, indeed - for my question, efficiency is not really relevant; I just wanted to know in principle how would a 'reverse readlink' be handled. Thanks again - cheers!Colitis
@Doge for speed, how about using find / -type l -ls? That skips non-symlinks, and avoids all per-file process creation (which makes it faster than my answer). P.s. the reason I'm so worried about speed is that my boot volume has 824,220 files on it, and if you create a process for each one of those... it's going to be a while.Donau
I've heard that processing the output of the 'ls' command is one of the seven deadly sins.Eltonelucidate
Yes, it's sometimes hard to do right, especially as some versions of ls make a habit of changing their output depending on whether they're sending to a terminal or not. And also because most people use it for doing something with file names without handling spaces. But this solution is not subject to those two issues and, provided you understand the format the data that ls produces, it's no more "evil" than any other command :-)Doge
C
175

Using GNU find, this will find the files that are hard linked or symlinked to a file:

find -L /dir/to/start -samefile /tmp/orig.file
Clavier answered 26/12, 2010 at 20:44 Comment(6)
Hi @Dennis Williamson, thank you, +1 for your answer! :) Btw, how does this relate to above "...target file contains zero information on what source files point to it..."; "...basically perform an ls -al on every file..."?? Does this command do basically the same (but maybe in more optimized manner) - or something fundamentally different? Thanks again, cheers!Colitis
@sdaau: The target file does not contain the information. This command is basically a brute-force search for files that link back to it.Clavier
Is there any equivalent way to do this with busybox find?Pincers
@g.rocket: To find the hardlinked files: find /dir/to/start -follow -inum $(stat -c %i /tmp/orig.file). You might be able to come up with something to find symlinks, but nothing quickly occurs to me.Clavier
is there a directory equivalent of this?Vilipend
@qodeninja: If the name that is the argument to -samefile is a directory, then the results will be directories (or symlinks to directories). If you want to be explicit, add -type d to the command after the position where the starting directory is specified, for example: find -L /dir/to/start type -d -samefile /tmp/orig.dirClavier
D
34

I've not seen a command for this and it's not an easy task, since the target file contains zero information on what source files point to it.

This is similar to "hard" links but at least those are always on the same file system so you can do a find -inode to list them. Soft links are more problematic since they can cross file systems.

I think what you're going to have to do is basically perform an ls -al on every file in your entire hierarchy and use grep to search for -> /path/to/target/file.

For example, here's one I ran on my system (formatted for readability - those last two lines are actually on one line in the real output):

pax$ find / -exec ls -ald {} ';' 2>/dev/null | grep '\-> /usr/share/applications'
lrwxrwxrwx 1 pax pax 23 2010-06-12 14:56 /home/pax/applications_usr_share
                                         -> /usr/share/applications
Doge answered 26/12, 2010 at 0:18 Comment(8)
Hi @Doge - likewise thanks for the clear answer; +accept for the reference to 'hard links' and 'find -inode'. Cheers!Colitis
Don't forget you can have symlinks on servers where the fs is nfs mounted. So it may not be enough simply to look at the local server.Holliehollifield
This won't work with either recursive links (links to links to...) or relative links (whatever -> ../../usr/share/applications). Handling those cases is harder. Also, it spawns a new instance of ls for every file checked, meaning it'll be pretty slow.Donau
@Gordon, you're right, it won't work as-is for longer chains but it's not clear to me that was a requirement. You could make a script which would follow chains like that if you wish but, like the NFS-mounted symlinks that dietbuddha mentioned, it another level of problems. As to the efficiency, it's well known (or at least answered elsewhere on SO) that find with xargs is better for efficiency but that's not relevant to the specific question here. If this turns out to be too slow then by all means ask how to make it faster. But, if it's a one-off sort of thing, it's probably not needed.Doge
Hi @dietbuddha, @Gordon Davisson, @Doge - thanks for writing down those issues! Just wanted to restate that, indeed - for my question, efficiency is not really relevant; I just wanted to know in principle how would a 'reverse readlink' be handled. Thanks again - cheers!Colitis
@Doge for speed, how about using find / -type l -ls? That skips non-symlinks, and avoids all per-file process creation (which makes it faster than my answer). P.s. the reason I'm so worried about speed is that my boot volume has 824,220 files on it, and if you create a process for each one of those... it's going to be a while.Donau
I've heard that processing the output of the 'ls' command is one of the seven deadly sins.Eltonelucidate
Yes, it's sometimes hard to do right, especially as some versions of ls make a habit of changing their output depending on whether they're sending to a terminal or not. And also because most people use it for doing something with file names without handling spaces. But this solution is not subject to those two issues and, provided you understand the format the data that ls produces, it's no more "evil" than any other command :-)Doge
P
6

Inspired by Gordon Davisson's comment. This is similar to another answer, but I got the desired results using exec. I needed something that could find symbolic links without knowing where the original file was located.

find / -type l -exec ls -al {} \; | grep -i "all_or_part_of_original_name"
Pippy answered 14/4, 2018 at 14:22 Comment(0)
A
2

Symlinks do not track what is pointing to a given destination, so you cannot do better than checking each symlink to see if it points to the desired destination, such as

for i in *; do
    if [ -L "$i" ] && [ "$i" -ef /tmp/orig.file ]; then
        printf "Found: %s\n" "$i"
    fi
done
Alphard answered 26/12, 2010 at 0:15 Comment(0)
D
0

Here's what I came up with. I'm doing this on OS X, which doesn't have readlink -f, so I had to use a helper function to replace it. If you have it a proper readlink -f you can use that instead. Also, the use of while ... done < <(find ...) is not strictly needed in this case, a simple find ... | while ... done would work; but if you ever wanted to do something like set a variable inside the loop (like a count of matching files), the pipe version would fail because the while loop would run in a subshell. Finally, note that I use find ... -type l so the loop only executes on symlinks, not other types of files.

# Helper function 'cause my system doesn't have readlink -f
readlink-f() {
    orig_dir="$(pwd)"
    f="$1"
    while [[ -L "$f" ]]; do
        cd "$(dirname "$f")"
        f="$(readlink "$(basename "$f")")"
    done
    cd "$(dirname "$f")"
    printf "%s\n" "$(pwd)/$(basename "$f")"
    cd "$orig_dir"
}

target_file="$(readlink-f "$target_file")" # make sure target is normalized

while IFS= read -d '' linkfile; do
    if [[ "$(readlink-f "$linkfile")" == "$target_file" ]]; then 
        printf "%s\n" "$linkfile"
    fi
done < <(find "$search_dir" -type l -print0)
Donau answered 26/12, 2010 at 5:16 Comment(1)
Hi @Gordon Davisson - thanks for the code; it will also be useful for Mac users! Cheers!Colitis
V
0
function list_symlinks() {
    oIFS="${IFS}"; IFS=$'\n';
    declare -a results=($(
        sudo find / -type l -ls 2>&1 \
        | grep -i " ${1}" \
        | sed -e 's/\ \{2,\}/\ /g' -e "s/^\ //" 
      ));
    for x in ${results[@]}; do
      [[ $(
            echo -e "${x}" \
            | grep -v -c 'No such file or directory'
          ) -gt 0 \
      ]] && echo -e "${x}" | cut -d' ' -f11-13 ;
    done; IFS="${oIFS}";
    return 0;
  }; alias list-symlinks='list_symlinks';

$ list-symlinks /bin/nc.openbsd ;

/etc/alternatives/nc -> /bin/nc.openbsd
/etc/alternatives/netcat -> /bin/nc.openbsd
Vichy answered 24/12, 2022 at 2:2 Comment(1)
Welcome to SO! Please don't post code-only answers but add a little textual explanation about how and why your approach works and what makes it different from the other answers given. You can find out more at our "How to write a good answer" page.Lamarckism
B
-1

This may be too simplistic for what you want to do, but I find it useful. it does Not answer your question literally, as it's not 'run on the original file', but it accomplishes the task. But, a lot more HDD access. And, it only works for 'soft' linked files which is majority of user linked files.

from the root of you data storage directory or users data directories, wherever symlinked 'files' to the orig.file may reside, run the find command:

# find -type l -ls |grep -i 'orig.file' 

or

# find /Starting/Search\ Path/ -type l -ls |grep -i '*orig*'

I would Normally use part of the name eg, '*orig*' to start, because we know users will rename (prefix) a simply named file with a more descriptive one like " Jan report from London _ orig.file.2015.01.21 " or something.

Note: I've Never gotten the -samefile option to work for me.

clean, simple, easy to remember

hope this helps Someone. Landis.

Balliol answered 20/12, 2015 at 22:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.