Locating all the scripts which start with:#!

Hi,
I need to find all the executable shell scripts under /home dirctory and its sub directories.
I would like to print the path to the files , which include in the header of the file: #!
(Actually its the first line of the file).

I have tried :

find . -name "*" -type f  -exec sh -c '
    case "$(head -n 1 "$1")" in
      #!*)exit0;;
    esac
exit 1
' sh {} \; -print

Its return nothing.
Please advise
Thanks

Quick and dirty longhand for the home directory only as a starter using OSX 10.7.5, default bash terminal:-

#!/bin/bash
> /tmp/listing
> /tmp/exe_scripts.txt
ls -l ~/*.sh > /tmp/listing
while read line
do
	if [ "${line:3:1}" = "x" ]
	then
		echo "$line" >> /tmp/exe_scripts.txt
	fi
done < /tmp/listing
cat /tmp/exe_scripts.txt

Results:-

Last login: Sun Apr 19 22:29:11 on ttys000
AMIGA:barrywalker~> cd ~/Desktop/Code/ShellAMIGA:barrywalker~/Desktop/Code/Shell> chmod 755 exescript.sh
AMIGA:barrywalker~/Desktop/Code/Shell> ./exescript.sh
-rwxr-xr-x  1 barrywalker  staff  128656 22 Nov 09:36 /Users/barrywalker/03080.sh
-rwxr-xr-x  1 barrywalker  staff  128990  6 Dec 22:18 /Users/barrywalker/03083.sh
-rwxr-xr-x  1 barrywalker  staff  131697  7 Dec 19:48 /Users/barrywalker/03090.sh
-rwxr-xr-x  1 barrywalker  staff  134772 29 Dec 21:05 /Users/barrywalker/03100.sh
-rwxr-xr-x  1 barrywalker  staff  135072 30 Jan 20:30 /Users/barrywalker/03120.sh
-rwxr-xr-x  1 barrywalker  staff  140746  3 Apr 11:32 /Users/barrywalker/03140.sh
-rwxr-xr-x  1 barrywalker  staff  143511 11 Apr 20:37 /Users/barrywalker/AudioScope.sh
AMIGA:barrywalker~/Desktop/Code/Shell> _

Try:

find . -type f -exec grep -n '^#!' {} + | awk -F: '$2==1{print $1}'

This does a recursive look, but does NOT display the directories, OS etc as before:-

#!/bin/bash
> /tmp/listing
> /tmp/exe_scripts.txt
cd ~
ls -lR > /tmp/listing
while read line
do
	if [ "${line:3:1}" = "x" ] && [ "${line:$((${#line}-3)):3}" = ".sh" ]
	then
		echo "$line" >> /tmp/exe_scripts.txt
	fi
done < /tmp/listing
cat /tmp/exe_scripts.txt

Results:-

Last login: Sun Apr 19 22:43:41 on ttys000
AMIGA:barrywalker~> cd ~/Desktop/Code/Shell
AMIGA:barrywalker~/Desktop/Code/Shell> ./exescript.sh
ls: com.solidstatenetworks.host.savedState: Permission denied
-rwxr-xr-x   1 barrywalker  staff  128656 22 Nov 09:36 03080.sh
-rwxr-xr-x   1 barrywalker  staff  128990  6 Dec 22:18 03083.sh
-rwxr-xr-x   1 barrywalker  staff  131697  7 Dec 19:48 03090.sh
-rwxr-xr-x   1 barrywalker  staff  134772 29 Dec 21:05 03100.sh
-rwxr-xr-x   1 barrywalker  staff  135072 30 Jan 20:30 03120.sh
-rwxr-xr-x   1 barrywalker  staff  140746  3 Apr 11:32 03140.sh
-rwxr-xr-x   1 barrywalker  staff  143511 11 Apr 20:37 AudioScope.sh
{SNIP}
-rwxr--r--   1 barrywalker  staff   63938 26 Apr  2014 02250.sh
-rwxr--r--   1 barrywalker  staff   93359 29 Jun  2014 02500.sh
-rwxr--r--   1 barrywalker  staff  106544 29 Jun  2014 02550.sh
-rwxr--r--   1 barrywalker  staff  108239 29 Jun  2014 03000.sh
-rwxr--r--@  1 barrywalker  staff  108463  9 Nov 10:19 03010.sh
-rwxr--r--@  1 barrywalker  staff  110523  9 Nov 10:20 03020.sh
-rwxr-xr-x   1 barrywalker  staff  115347  9 Nov 18:46 03040.sh
-rwxr--r--   1 barrywalker  staff  128656 22 Nov 09:36 03080.sh
-rwxr--r--   1 barrywalker  staff  131697  7 Dec 19:48 03090.sh
-rwxr--r--   1 barrywalker  staff  134772 29 Dec 21:05 03100.sh
-rwxr--r--   1 barrywalker  staff  135072 30 Jan 20:30 03120.sh
-rwxr--r--@  1 barrywalker  staff  135072 30 Jan 20:14 AudioScope.sh
-rwxr--r--  1 barrywalker  staff   63938 26 Apr  2014 AudioScope.sh
-rwxr--r--  1 barrywalker  staff   52320  5 Jan  2014 AudioScope_02-01-2014.sh
-rwxr--r--  1 barrywalker  staff    4333 26 Mar  2014 Function_Generator.sh
AMIGA:barrywalker~/Desktop/Code/Shell> _

Perhaps this might be a little quicker, and certainly supports files with spaces in their name:

find . -type f -print0 | xargs -0 awk '/^#!/&&FNR==1{print FILENAME}{next}'

Or if you have GNU awk use nextfile :

find . -type f -print0 | xargs -0 gawk '/^#!/&&FNR==1{print FILENAME}{nextfile}'
1 Like

You're correct in noting that grep and awk aren't both needed; the pattern matching I was doing with grep can just as well be done inside awk .

Note that my earlier suggestion didn't have any problem with spaces (or tabs) in filenames, but it wouldn't work with filenames containing one or more colon characters.

Note also that we haven't been told what OS the submitter is using (and the -0 option in xargs and the -print0 primary in find are not available on all systems (and are extensions not included in the POSIX standards).

The awk nextfile function is also an extension to the POSIX standards, but isn't just available in gawk . The following should be portable to any Linux or UNIX system:

find . -type f -exec awk 'FNR == 1 && /^#!/{print FILENAME}' +

If you want to try this on a Solaris/SunOS system, change awk to /usr/xpg4/bin/awk . If awk on your system has a nextfile function, the following will run considerably faster:

find . -type f -exec awk 'FNR == 1{if($0 ~ /^#!/) print FILENAME; nextfile}' +

Hmm, trust me not to read the OP's post fully. Apologies.

But I am curious though, much like my attempt assumes a '.sh' extension what about a shell script that does not have a shebang as the first line?

How do you cater for such circumstances?

You don't.

Is the following file ( scf ) a shell script?:

$ ls -l scf
-------r--  1 dwc  staff  35 Apr 20 01:27 scf
$ cat scf
supercalifragilisticexpialidocious

It can be for someone whose user name is not dwc , does not have a primary group name or supplementary group name staff , and has an executable file named supercalifragilisticexpialidocious somewhere in their search path.

1 Like

Although not the more efficient, your first attempt was pretty close. The main issue is your case line was ignored interpreted as a comment, starting with a "#".
I also removed '-name "*"' which is useless being always true.

find . -type f  -exec sh -c '
    case "$(head -n 1 "$1")" in
      \#!*) exit 0;;
    esac
exit 1
' sh {} \; -print

Answering to your last question, I would start with the "file" command. There is no simple heuristic to sort out shell scripts from other text files so most non shebanged scripts will show up as "ASCII text" with this command:

find . -type f -exec file {} + | grep text

There might be versions of find where -name "*" is everything but .* .
If this is intended then \! -name ".*" would be more clear.

deleted by author

According to the standards, -name "*" should match every path being evaluated. But, note that the entries . and .. are not evaluated unless they were named as operands on the command line.

You really can't.

All a user needs to do is source a file anyway. In bash:

bash 4.1$ . TextFile

That'll run through commands in TextFile just as if it were executable and started with "#!/bin/bash".

Not sure what the point of finding scripts is anyway, since all they do is the same thing the user can do with a command line.

And what about files .bashrc or .profile?

Check that the file is a text file that is executable?

No, that won't work. I realised that early on...
Take this as an example, saved as /some/path/HW.

echo "Hello www dot unix dot com."
echo "So executable mode is not needed. <wink>"

Results on OSX 10.7.5, default bash terminal.

Last login: Wed Apr 22 18:35:27 on ttys000
AMIGA:barrywalker~> cd Desktop/Code/Shell
AMIGA:barrywalker~/Desktop/Code/Shell> chmod 400 HW
AMIGA:barrywalker~/Desktop/Code/Shell> ./HW
-bash: ./HW: Permission denied
AMIGA:barrywalker~/Desktop/Code/Shell> . ./HW
Hello www dot unix dot com.
So executable mode is not needed. <wink>
AMIGA:barrywalker~/Desktop/Code/Shell> chmod 755 HW
AMIGA:barrywalker~/Desktop/Code/Shell> ./HW
Hello www dot unix dot com.
So executable mode is not needed. <wink>
AMIGA:barrywalker~/Desktop/Code/Shell> _

As Don and achenle have already pointed out __any__ method of detection does not work with reliability.
Also #! or #!/ is a non-starter too, which defeats the object of the OP.
If all the OP wants are the shell scripts that begin with a shebang and/or their drawers then that is indeed possible but I wonder if the OP was aware of the above?
Apoloigies for hijacking this thread, I am clear now...