1

I am in a root directory and have some folders inside:

0.1
0.2
0.3
0.4
0.5
0.6
shortcut -> 0.6

I need to list the above directories without both shortcut as well as 0.6 folder. I don't won't to search above this location or within any of these folders. I might have some files here as well but I need to ignore them. New folders with the same naming convention will be added from time to time in this directory, hence this search will be included in the bash script and will generate different results when new folders will be added and script will be run.

I've tried find -P . -maxdepth 1 -type d -ls but no luck.

  • What do you mean by "be live"? – Kusalananda Aug 10 '18 at 18:58
  • New folders will be created there from time to time by different user. – mk4965 Aug 10 '18 at 18:59
  • Sure, but you say "this search needs to be live", and I don't know what that means. – Kusalananda Aug 10 '18 at 19:01
  • Good point "live" was really a bad descriptor there, I've just changed it. It is a part of the script that will be producing different results as new folders will be added. – mk4965 Aug 10 '18 at 19:08
2

Try using the prune switch for excluding directories, this should answer your question:

find . -path ./your-folder -prune -o -maxdepth 1 -type d -print
  • 1
    -name '*' does not do anything as it's always true. – Kusalananda Aug 10 '18 at 19:27
  • I agree forgot to remove it – JammingThebBits Aug 10 '18 at 19:28
  • 2
    @Kusalananda, technically, with several find implementations including GNU find, -name '*' excludes the files whose name contains sequences of bytes not forming valid characters in the current locale. IOW, it selects files whose name is valid text in the current locale. – Stéphane Chazelas Aug 10 '18 at 20:38
  • @StéphaneChazelas When it comes to locale-related things, I'm trusting you more than myself. – Kusalananda Aug 10 '18 at 20:41
  • What is ./f? How does this avoid the target of the symbolic link? – Kusalananda Aug 13 '18 at 12:05
4

There is no way of knowing what names are the targets of symbolic links, other than to find the symbolic links and follow them.

We can therefore do this in this way (assumes bash version 4.0 or later):

#!/bin/bash

# Our blacklist and whitelist associative arrays (whitelist is a misnomer, I know)
# blacklist: keyed on confirmed targets of symbolic links
# whitelist: keyed on filenames that are not symbolic links
#            nor (yet) confirmed targets of symbolic links

declare -A blacklist whitelist

for name in *; do
    if [ -L "$name" ]; then

        # this is a symbolic link, get its target, add it to blacklist
        target=$(readlink "$name")
        blacklist[$target]=1

        # flag target of link in whitelist if it's there
        whitelist[$target]=0

    elif [ -z "${blacklist[$name]}" ]; then
        # This is not a symbolic link, and it's not in the blacklist,
        # add it to the whitelist.
        whitelist[$name]=1
    fi
done

# whitelist now has keys that are filenames that are not symbolic
# links. If a value is zero, it's on the blacklist as a target of a
# symbolic link.  Print the keys that are associated with non-zeros.
for name in "${!whitelist[@]}"; do
    if [ "${whitelist[$name]}" -ne 0 ]; then
        printf '%s\n' "$name"
    fi
done

The script is supposed to be run with your directory as the current working directory, and it makes no assumptions about the names in that directory.

  • In unset whitelist[$target], whitelist[$target] is a glob, so would need to be quoted, but even then, unset whitelist"[$target]" wouldn't work for some values of $target like those containing ] and would even be a command injection vulnerability (try for instance: x='`uname>&2`x' bash -c 'typeset -A a; a[$x]=1; unset "a[$x]"'. You can actually do unset 'whitelist[$target]' though I can't tell if it would be guaranteed to work in future versions, as that's currently not documented. – Stéphane Chazelas Aug 11 '18 at 9:10
  • Note that that approach assumes the target is expressed as 0.6, not ./0.6 or /path/to/0.6. – Stéphane Chazelas Aug 11 '18 at 9:15
3

If you mean you want all the files of type directory that are not the target of the shortcut symlink, with zsh:

#! /bin/zsh -
printf '%s\n' *(/^e'{[[ $REPLY -ef shortcut ]]}')
  • (...): glob qualifier, to further filter files based on other criteria than just name
  • /: only files of type directory
  • ^: negate the following glob qualifiers
  • e'{shell code}': select files based on the result (exit status) of the evaluation of the shell code (where the files being considered is in $REPLY)
  • [[ x -ef y ]]: returns true if x and y point to the same file (after symlink resolution). Typically, it does that by comparing the device and inode number of both files (obtained with a stat() system call that resolves symlinks).

With GNU find (list not sorted, file names prefixed with ./):

#! /bin/sh -
find -L . ! -name . -prune -xtype d ! -samefile shortcut
  • -L: for symlinks, the target of the symlink is considered. That's needed for -samefile to do the same thing as zsh's -ef above.
  • ! -name . -prune: prune any file but .. Same as -mindepth 1 -maxdepth 1 but shorter and standard.
  • -xtype d: now that -L is on, we need -xtype to match the type of the original file before symlink resolution:
  • -samefile shortcut: true if the file is the same as shortcut (after symlink resolution with -L)

To list all directories except those that are the target of any of the symlinks in the current directories:

#! /bin/zsh -
zmodload zsh/stat
typeset -A ignore
for f (*(N@-/)) {
   zstat -H s -- $f &&
     ignore[$s[device]:$s[inode]]=1
}

printf '%s\n' *(/^e'{zstat -H s -- $REPLY && ((ignore[$s[device]:$s[inode]]))}')

Note that the zsh-bases ones ignore the hidden files. Add the D glob qualifier or set the dotglob option to consider them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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