0

I want to run a command that outputs all the hidden files the current user can read - in Bash.

So far, I am using the find command, with -perm but I can't seem to get files the current user can read, but files a member of current user's GROUP can read. For testing purposes I am only looking in the /home/ dir. This is achieved with:

find /home/ ! -perm u+r -regextype egrep -regex '^(/.+)*/\..*')' 2>/dev/null

I don't want to use the -readable flag, since it isn't supported in all implementations of find

Is there some magic permissions octal I can use to do this?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
berzerk0
  • 11
  • 6
  • Not really a bash question -- the `find` options available aren't shell-specific (indeed, you can use `find` without any shell at all). Might perhaps retag this to `unix` + `findutils`. – Charles Duffy Oct 23 '17 at 19:24

1 Answers1

1

The relevant standard to which all find implementations must comply is http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html.

That said, -readable was added as an extension to the standard because it adds capabilities that standard find doesn't have. To do without it, we need to compromise either performance or correctness.


Correct But Slow

If you really want to be 100% correct in all cases, consider doing the test external to find, as follows:

find /home -type f -name '.*' '(' -exec test -r '{}' \; ')' -print

This way we're actually testing (vs calculating) readability for every file.


Less Slow (Using Shell-Builtin Evaluation)

find /home -type f -name '.*' -exec sh -c '
  for filename; do test -r "$filename" && printf "%s\n" "$filename"; done
' _ {} +

Instead of starting one copy of test per file, this starts one sh per batch of files (size of each batch depending on the number of filenames that can fit on a command line) and uses the copy of test built into that sh instance to evaluate, and print, those names.


Fast But Imperfect

The following only runs the above, expensive, test on files that don't look readable on account of their explicit file permissions:

find /home -type f -name '.*' \
  '(' \
    '(' \
      '(' -perm -0004 ')' -o \
      '(' -group "$(id -g)" -perm -0040 ')' -o \
      '(' -user  "$(id -u)" -perm -0400 ')' \
    ')' -o '(' -exec test -r '{}' \; ')' \
  ')' -print

That is to say:

  • -perm 0004 matches all files which are world-readable
  • -group "$(id -g)" -perm -0040 matches all files which have the current user's primary group as their group and are group-readable.
  • -user "$(id -u)" -perm -0400 matches all files which are owned by the current user.
  • The final -exec test -r '{}' \; filters for files that are readable despite having passed none of the above tests (for instance, on account of secondary group membership). This test is slow, so we're running it only on files that don't look readable by other means.

This may be inaccurate in unusual corner cases, such as files that have ACLs or other extended permissions modifying their readability.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • isn't missing a `-iname ".*"` to find all hidden files? – nbari Oct 23 '17 at 19:38
  • Heh. I was focusing on what struck me as the interesting part of the question, but you're right -- that *is* an element. (No point to `-iname` vs `-name`, though). – Charles Duffy Oct 23 '17 at 19:39
  • I just ran your "Correct but slow" version, and it seems to work well at finding hidden files in the home directory the user can NOT read. I'm not familiar with the syntax you used, is there a simple way to invert the logic? – berzerk0 Oct 23 '17 at 19:46
  • @berzerk0, ...fixed. Sorry 'bout that, my fault for skimming the question a little too quickly. – Charles Duffy Oct 23 '17 at 19:48
  • @CharlesDuffy I am running `find /home -name '.*' '(' -exec test -r '{}' \; ')' -print` and it finds all the hidden files, but not just ones the user can read. For an example of what I am looking to do, as User A, I want to see if a file like User B's .bash_history is readable. If it is, I want it listed, if it's not, I don't want it listed. – berzerk0 Oct 23 '17 at 19:51
  • As User_A, if you run `test -r /home/User_B/.bash_history; echo $?`, it should be returning `0` if and only if the current user (A) can read that file. This is what you see in practice, correct? – Charles Duffy Oct 23 '17 at 19:52
  • ...frankly, my first instinct re: the above is to think that perhaps the script is being run with more permissions than you want it to (ie. running as root, not User_A). – Charles Duffy Oct 23 '17 at 19:54
  • Strangely no. It returns a `1`, but that file cannot be viewed with `cat. ` it also seems to return a `1` even if that file does not exist, by mistake I misspelled User_B's name and it still returned a `1`. – berzerk0 Oct 23 '17 at 19:55
  • Yes, `1` (indicating a failure) is what we'd expect if the file doesn't exist, or if it otherwise isn't readable. If the test returns `1`, then `find` should treat that as a failure and not run later predicates (ie. `-print`). – Charles Duffy Oct 23 '17 at 19:56
  • (To be clear, by the way -- this code *does* work for me, with both GNU and BSD `find` implementations; if I go into a temporary directory, create one dotfile I can read and one dotfile I can't, and then run `find . -type f -name '.*' '(' -exec test -r '{}' \; ')' -print`, only the name of the readable dotfile is emitted). – Charles Duffy Oct 23 '17 at 19:58
  • Are you perhaps leaving out `-print` and relying on that being the default action? That's not safe with our current usage. – Charles Duffy Oct 23 '17 at 19:59
  • Okay, `find /home -name '.*' '(' -exec test -r '{}' \; ')' -print ` does in fact work. I was initially testing on an intentionally misconfigured CTF VM, but now I am running it on a properly configured machine and that seems to work. Thanks! – berzerk0 Oct 23 '17 at 20:03