66

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

7 Answers 7

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
6
  • 1
    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!
    – sdaau
    Commented Jan 4, 2011 at 8:40
  • 9
    @sdaau: The target file does not contain the information. This command is basically a brute-force search for files that link back to it. Commented Jan 4, 2011 at 15:26
  • Is there any equivalent way to do this with busybox find? Commented Jul 20, 2016 at 21:19
  • @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. Commented Jul 20, 2016 at 21:54
  • 1
    @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.dir Commented Aug 19, 2019 at 21:53
33

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
8
  • Hi @paxdiablo - likewise thanks for the clear answer; +accept for the reference to 'hard links' and 'find -inode'. Cheers!
    – sdaau
    Commented Dec 26, 2010 at 0:40
  • 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.
    – snakehiss
    Commented Dec 26, 2010 at 1:20
  • 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. Commented Dec 26, 2010 at 5:03
  • 7
    @paxdiablo 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. Commented Dec 26, 2010 at 20:15
  • 4
    I've heard that processing the output of the 'ls' command is one of the seven deadly sins.
    – JeffG
    Commented Feb 24, 2012 at 16:28
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"
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
0
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)
1
  • Hi @Gordon Davisson - thanks for the code; it will also be useful for Mac users! Cheers!
    – sdaau
    Commented Dec 26, 2010 at 10:34
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
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.
    – ahuemmer
    Commented Dec 26, 2022 at 20:03
-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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.