LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - General (http://www.linuxquestions.org/questions/linux-general-1/)
-   -   bash script to find files with shebang and then chmod (http://www.linuxquestions.org/questions/linux-general-1/bash-script-to-find-files-with-shebang-and-then-chmod-686031/)

nshewmaker 11-25-2008 02:13 PM

bash script to find files with shebang and then chmod
 
I want to look for all the files in a directory, and examine the first line of each for a shebang ("#!"). I can get close with the following command:

Code:

find dir/ -user $UID -type f -exec grep --files-with-matches --binary-files=without-match --max-count=1 --perl-regexp "^#\!\s*?\/" {} \; | xargs -r chmod -cf 755
This works, but it inefficiently examines the entire file until the first match is found, if any. Additionally, the shebang could appear on any line, which is invalid. I'd like to use the "head" command to crop only the first line, but I don't know how to do that in one step, keeping the filename needed for the second part of the operation (chmod).

I could handle this with a "higher-level" script, but I'd like to stick with bash, if possible. Any bash shell gurus out there with a solution?

0.o 11-25-2008 03:28 PM

Quote:

Originally Posted by nshewmaker (Post 3354063)
I want to look for all the files in a directory, and examine the first line of each for a shebang ("#!"). I can get close with the following command:

Code:

find dir/ -user $UID -type f -exec grep --files-with-matches --binary-files=without-match --max-count=1 --perl-regexp "^#\!\s*?\/" {} \; | xargs -r chmod -cf 755
This works, but it inefficiently examines the entire file until the first match is found, if any. Additionally, the shebang could appear on any line, which is invalid. I'd like to use the "head" command to crop only the first line, but I don't know how to do that in one step, keeping the filename needed for the second part of the operation (chmod).

I could handle this with a "higher-level" script, but I'd like to stick with bash, if possible. Any bash shell gurus out there with a solution?

Code:


for i in `find dir/ -user $UID -type f`;
    do
          t=`head $i | grep #!`;
                if [ $? -eq 0 ];
                    echo $i;
    done


uberNUT69 11-25-2008 07:51 PM

find dir/ -user $UID -type f | while read fn; do head -n1 "$fn" | grep -q "^#\!" && echo "$fn" && chmod 755 "$fn"; done

0.o 11-26-2008 09:34 AM

Quote:

Originally Posted by uberNUT69 (Post 3354451)
find dir/ -user $UID -type f | while read fn; do head -n1 "$fn" | grep -q "^#\!" && echo "$fn" && chmod 755 "$fn"; done


That's going to output the results of the grep command and head. I find it much easier to redirect that to a temp variable and output just the file name.

Telemachos 11-26-2008 10:04 AM

This appears to work without the overhead of find (seems like overkill to me here), but frankly this would be trivial in Perl (or many other languages), so I'm not sure why stick with "pure" Bash.
Code:

#!/bin/bash
for item in testy/*
do
  if [ -f $item ]; then
    head -n1 $item | grep -q '^#!'
    if [ $? -eq 0 ]; then
      chmod +x $item
    fi
  fi
done


uberNUT69 11-26-2008 10:30 AM

Quote:

Originally Posted by 0.o (Post 3355361)
That's going to output the results of the grep command and head. I find it much easier to redirect that to a temp variable and output just the file name.

You haven't tried it.

In my suggestion above:
a) stdout from head is piped into grep, and grep is silent (-q). (only the status from grep is used)
b) the filename is echoed for your "convenience" ;) (ie: not necessary!)


Quote:

Originally Posted by Telemachos (Post 3355412)
This appears to work without the overhead of find (seems like overkill to me here), but frankly this would be trivial in Perl (or many other languages), so I'm not sure why stick with "pure" Bash.

Because it's a one-(command-)liner ;).

Quote:

Originally Posted by Telemachos (Post 3355412)
Code:

#!/bin/bash
for item in testy/*
do
  if [ -f $item ]; then
    head -n1 $item | grep -q '^#!'
    if [ $? -eq 0 ]; then
      chmod +x $item
    fi
  fi
done


except that:
a) * is not recursive
b) your solution doesn't check for files owned by $USER
c) you can have situations where you get "too many arguments"
... (ie: * creates command line exceeding buffer if too many files).

your solution is still a one-liner (filenames quoted)

$ for item in dir/*; do [ -f "$item" ] && head -n1 "$item" | grep -q '^#!' && chmod +x "$item"; done

nshewmaker 11-26-2008 11:15 AM

Quote:

Originally Posted by Telemachos (Post 3355412)
This appears to work without the overhead of find (seems like overkill to me here), but frankly this would be trivial in Perl (or many other languages), so I'm not sure why stick with "pure" Bash.

I fully intend to replace the bash script needing this bit of code with a Perl one. However, it's used in a delicate way, and I'd rather fix this single problem instead of potentially starting a new set of them.

Telemachos 11-26-2008 11:28 AM

Quote:

Originally Posted by nshewmaker (Post 3355511)
I fully intend to replace the bash script needing this bit of code with a Perl one. However, it's used in a delicate way, and I'd rather fix this single problem instead of potentially starting a new set of them.

If that works for you, cool. I would find it easier to use one tool and make the solution work there, rather than throwing together a one-liner now and fixing it later (with a different language).

nshewmaker 11-27-2008 02:38 PM

Quote:

Originally Posted by uberNUT69 (Post 3354451)
find dir/ -user $UID -type f | while read fn; do head -n1 "$fn" | grep -q "^#\!" && echo "$fn" && chmod 755 "$fn"; done

This works great! Thanks, uberNUT. I'm glad you understand the appeal of one-liners. ;)


All times are GMT -5. The time now is 01:24 PM.