Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Streamline bash script?

Tags:

bash

There has to be a way to stream line those 3 find lines in my crudely constructed script below...

#!/usr/bin/bash
#
#
if [ "find /data -type f -perm 400 -print" ] ; then
    find /data -type f -perm 400 -exec echo Modifying {} \;
    find /data -type f -perm 400 -print | xargs chmod 755
else
    echo 'No files to modify'
fi
like image 758
user1827698 Avatar asked Feb 19 '23 12:02

user1827698


2 Answers

You can change it to the following:

found=$(find /data -type f -perm 400 -print -quit)

if [ "$found" ]; then
    find /data -type f -perm 400 -exec echo Modifying {} \; -exec chmod 755 {} \; 
else
    echo 'No files to modify`
fi

Explanation:

  • find /data -type f -perm 400 -print -quit: As soon as this finds the very first file with permission 400 in /data, it prints the file name and terminates the find immediately. The print is saved to the variable found (So we know whether to output "No files to modify" or not later).
  • -exec echo Modifying {} \; -exec chmod 755 {} \;: You can execute multiple commands on each result returned by find through this chaining syntax
  • Note: apparently find returns 0 when nothing is found, so using $? doesn't work here.
like image 183
sampson-chen Avatar answered Feb 21 '23 01:02

sampson-chen


while read -d $'\0' file; do
    found=yes
    echo "Modifying $file"
    chmod 755 "$file"
done < <(find /data -type f -perm 400 -print0)
if [ "x$found" != "xyes" ]; then
    echo "No files found to modify"
fi

This uses the process substitution feature in Bash. It's equivalent to

find ... | while read ...

except in that case, the while read is performed in a subshell, so we can't set variables that will be seen in the outer shell. Instead, we can use process substitution. <(cmd) runs cmd in a subshell, with its standard output redirected into a named pipe. The name of that pipe is substituted into the outer command.

We then use < to redirect from this pipe into standard input of the while read command, which reads each delimited record in turn, and stores the value in the name specified (file in this case). By default, read breaks on newlines. For most files, that would be fine; you could do:

while read file; do
    ...
done < <(find /data -type f -perm 400)

and it would work fine. But it is technically possible to have a newline in a filename, in which case that would break. The -print0 argument to find, and -d $'\0' argument to read, causes them each to use a null character as a delimiter, which is not valid in a filename.

like image 20
Brian Campbell Avatar answered Feb 21 '23 00:02

Brian Campbell