0

So I'm a beginner and i have a project due next week. I have to print only the filename of the files that contain #!/bin/bash on the first line. So far I tried this

head -n 1 $filename | grep -l "pattern"

but when I execute it instead of the name of the files I receive

(standard input)

Like I said, I'm a beginner and so far I'm familiar with simple commands. So I would like to know where I f****** up and if there is a way of acheiving what I want without using harder commands like awk.

for fis in  find -perm -a+x -name "*.sh" -type f
do
head -n 1 $fis | grep -l "#!/bin/bash"
done
Hauke Laging
  • 88,146
  • 18
  • 125
  • 174
Laura
  • 1
  • 1
  • 1
    First, edit your question and use codeblocks. Second, add sample text and the expected output again using codeblocks. – Nasir Riley May 21 '20 at 22:45
  • You probably know that your command alone will examine only one file. Do you have a loop behind it? Or a find? Also, doing that will be very simple with awk, I must say :) – Quasímodo May 21 '20 at 22:56
  • Thank you for the advice, this is my first post and I don t really know how to use this platform... i hope that now my post is better. Any other suggestions are welcome :) – Laura May 21 '20 at 22:57
  • @Quasímodo yes, i do have a `for` to select only the executable files with the format *.sh – Laura May 21 '20 at 22:59
  • @Quasímodo I edited the loop in the initial question. I m a little bit skeptical of using awk because i never used it before but if it is the only way, that I will do it – Laura May 21 '20 at 23:10
  • 1
    Please consider [Why is looping over find's output bad practice?](https://unix.stackexchange.com/q/321697) – Quasímodo May 21 '20 at 23:16

4 Answers4

3

In GNU grep you can use the --label option to set the label/filename if the input comes from stdin.

Instead of using an outer loop with a command substitution, you could use the -exec action to start a small script to loop over the filenames and execute the commands.

find -perm -a+x -name "*.sh" -type f -exec sh -c '
  for fis; do
    head -n1 "$fis" | grep --label="$fis" -Fl "#!/bin/bash"
  done
' sh {} +

I added the -F option to grep since we're looking for a fixed string.

Freddy
  • 25,172
  • 1
  • 21
  • 60
2

I know you have blacklisted awk, but for other people seeking help who come across your question, this is a quite efficient way to print the names of the executable .sh files that contain #!/bin/bash in their first line:

find . -perm -a+x -name '*.sh' -type f -exec \
    awk 'NR == 1{ if ($0 == "#!/bin/bash") print FILENAME; exit }' {} \;

Or, to print the name of files that have #!/bin/bash in their first line without further restrictions, drop the -perm and -name options:

find . -type f -exec awk ...
Ed Morton
  • 28,789
  • 5
  • 20
  • 47
Quasímodo
  • 18,625
  • 3
  • 35
  • 72
  • @steeldriver More than that, it's the correct way, otherwise files with `#!/bin/bash` in 2nd line or after (and not in the 1st line) would also be printed. Thank you for the correction. – Quasímodo May 22 '20 at 03:43
  • @steeldriver True as well! I've set it as Community Wiki in case you see any more possible improvement. – Quasímodo May 22 '20 at 12:00
1
find . -perm -a+x -name '*.sh' -type f |
    while IFS= read -r file; do
        test -f "$file" || continue
        if head -n 1 "$file" | grep -qFx '#!/bin/bash'; then
            echo "$file"
        fi
    done
Hauke Laging
  • 88,146
  • 18
  • 125
  • 174
  • YES!! oh my god thank you so much... I can t express in words how grateful I am. this works!! – Laura May 21 '20 at 23:24
  • @Laura I modified the files it is workingt on. Freddy's `find` approach is less readable but safer (regarding "evil" characters in file and directory names). – Hauke Laging May 21 '20 at 23:31
  • See https://mywiki.wooledge.org/BashFAQ/001 for how to read the output of a command using a loop without a pipe. Not sure what the point of testing for `file` being a file is when `find` is outputting file names. – Ed Morton May 26 '20 at 00:34
  • @EdMorton That is not about testing the file object type again but about detecting problems resulting from newlines in path names as that is not a safe `-print0` solution. – Hauke Laging May 26 '20 at 00:48
  • 1
    If your file names can contain newlines then `test -f "$file" || continue` isn't a solution as it'll silently not test real files output by `find` and will let `head ...` execute on files that weren't output by `find` but might exist and share a common pre-newline part with the real file names (eg. find outputs `foo\nbar.sh` and there's a file named `foo` in the directory). IMHO you should just say "this won't work if your file names contain newlines" and get rid of that test line. – Ed Morton May 26 '20 at 00:57
0

with -n and passing multiple files the output will be in this format:

file_name:line_number:matched_text

-x matches the whole line

grep '#!/bin/bash' -nx *.sh |cut -d ':' -f 1,2 | grep ":1$" | cut -d ':' -f 1

binarysta
  • 2,912
  • 10
  • 14
  • Thank you for your suggestion, but I just tried it and it displays the numar of files that contains the pattern on the first line... i need the name of the filles :( – Laura May 21 '20 at 23:04
  • no this is correct, could you please try again? I added some details. – binarysta May 21 '20 at 23:05
  • @Quasímodo no the output will have only lines with `#!/bin/bash` from first `grep`. – binarysta May 21 '20 at 23:10
  • @Quasímodo yes, I edited. Thanks for mentioning – binarysta May 21 '20 at 23:16
  • Thank you for your effort, I really appreciate it and hopefully it will help others, it just didn't work for my program.. – Laura May 21 '20 at 23:25
  • @Laura thanks but what is the issue? this is just `grep` and `cut` should work – binarysta May 21 '20 at 23:27
  • I don't know... but the code from the other person that answered my questions worked immediately. – Laura May 21 '20 at 23:29
  • Maybe this doesn't work with the rest of my program, I really don't know. Programming is way harder than I thought.. – Laura May 21 '20 at 23:30
  • 1
    @EdMorton if multiple files passed to `grep -n` the output will include the filenames too, as I have mentioned in the answer. but we can add `-H` (not `-h`) to make it more clear. Thanks for other points, I fixed it. – binarysta May 26 '20 at 04:49