Friday, March 18, 2022

[SOLVED] (How) is it possible to add the x permission only to those who already have the r permission using standard shell commands and/or Unix utils?

Issue

I want to write a shell script that will operate on multiple files, and (among other things) will make them executable.

The problem: the files have different permissions – in particular, some are readable by all users and some are only readable by the owner – and I want to add the execution permission exactly for those who've got the read permission (that is, if all can read the file, all should be able to execute it, but if only the owner can read it, then only the owner should be able to execute it).

I know how to achieve this by writing a small program in any number of programming languages, and using a few different approaches (from a naive, straight-forward condition on the permissions for each agent to a tricky use of masking and shifting of the permission bits), but I'd like to achieve this in a "pure" shell script, using nothing but standard shell commands and Unix utils.

UPDATE: There are currently two answers, both of which suggesting interesting techniques (using zsh's glob qualifiers, and using find) which seem closely related to what I need, but slightly missing the point of the question, as the challenge I'm facing is not finding which files to work on: the user of the script will tell it which files to work on. Rather, the challenge is, given a specific file, to decide, for example, whether to call chmod o+x on it or not, based on whether the o+r permission is already set.


Solution

The answers provided so far present useful ideas, but, as none of them answers the question as asked, I am hereby posting an answer to my own question.

This answer presents three different solutions, each based on a different approach suggested in the previously posted answers; there are other ways too which are not covered (I'll only mention, without showing the actual code, the trick of getting the permissions as an octal number, and then – using some mechanism of performing arithmetic/bit-wise calculations – anding it with 444 to get the r bits, shifting right 2 bits to get the corresponding x bits, oring with the original permissions, and setting the result as the new permissions).

All of the solutions below assume working on the file "$FILE".

Solution 1 – ifing on the pattern of the current permissions as text

This is perhaps the most straightforward approach:

permissions=$(stat -f %SA "$FILE")
if [[ $permissions =~ .r........ ]]; then chmod u+x "$FILE"; fi
if [[ $permissions =~ ....r..... ]]; then chmod g+x "$FILE"; fi
if [[ $permissions =~ .......r.. ]]; then chmod o+x "$FILE"; fi

Solution 2 – using find, taking advantage of the -perm and -exec options

This approach uses find not to find multiple files, but rather to either find the given file (in case it has a certain permission) or to not find anything (otherwise), and to set the matching desired permission in case the file is found (and do nothing otherwise):

find "$FILE" -perm -u=r -exec chmod u+x {} \;
find "$FILE" -perm -g=r -exec chmod g+x {} \;
find "$FILE" -perm -o=r -exec chmod o+x {} \;

The three cases can easily be combined into a single for loop:

for w in u g o; find "$FILE" -perm -$w=r -exec chmod $w+x {} \;; done

Solution 3 (zsh only) – using glob qualifiers

This approach runs each of the relevant chmod commands on "$FILE" only in case it already matches the corresponding permission. Note || : is added to avoid errors in case there's no match, i.e. if the file does not have the corresponding permission (also note zsh still emits a no matches found warning in this case):

chmod u+x "$FILE"(r) || :
chmod g+x "$FILE"(A) || :
chmod o+x "$FILE"(R) || :


Answered By - Tom
Answer Checked By - Clifford M. (WPSolving Volunteer)