This question already has an answer here:

I need to write a shell script that find and print all files in a directory which starts with the string: #include. Now, I know how to check if a string is in the file, by using:

for f in `ls`; do
    if grep -q 'MyString' $f; then:
        #DO SOMETHING
    fi

but how can I apply this to the first line? I thought to maybe create a variable of the first line and check if it starts with #include, but I'm not sure how to do this. I tried the read command but I fail to read into a variable.

I'd like to hear other approaches to this problem; maybe awk? Anyway, remember, I need to check if the first line starts with #include, not if it contains that string. That's why I found those questions: How to print file content only if the first line matches a certain pattern? https://stackoverflow.com/questions/5536018/how-to-print-matched-regex-pattern-using-awk they are not completely helping.

marked as duplicate by don_crissti, elbarna, RalfFriedl, mosvy, G-Man yesterday

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 5
    Tip: don't use ls in scripts, it invariably leads to problems. – ctrl-alt-delor Nov 9 at 10:02
  • I think unix.stackexchange.com/a/232655/117549 is reasonably close -- just anchor the pattern. – Jeff Schaller Nov 9 at 11:28
  • @ctrl-alt-delor so I shall use "in *" instead? no problem... but what kind of problem 'ls' can cause? – Z E Nir 2 days ago
  • It is just that ls is hard to parse, it was not designed for the computer to read, but for humans. It is often used when not needed. There are other tools: just that are designed for the job: *, find, … – ctrl-alt-delor 2 days ago
  • @ctrl-alt-delor Thanks! – Z E Nir yesterday
up vote 7 down vote accepted

It is easy to check if the first line starts with #include in sed:

sed -n '1{/^#include/p};q'   file

Or simplified:

sed -n '/^#include/p;q'   file

That will have an output only if the file contains #include in the first line. That only needs to read the first line to make the check, so it will be very fast.

So, a shell loop for all files (with sed) should be like this:

for file in *
do
    [ "$(sed -n '/^#include/p;q' "$file")" ] && printf '%s\n' "$file"
done

If there are only files (not directories) in the pwd.

If what you need is to print all lines of the file, a solution similar to the first code posted will work:

sed -n '1{/^#include/!q};p'   file
  • it prints the name of the file/s, not its content... I just replaced the 'printf' with "cat $file" and it worked! Thanks! – Z E Nir Nov 9 at 9:58
  • @ZENir good idea, have you tried it? – ctrl-alt-delor Nov 9 at 10:04
  • @ctrl-alt-delor Yes, it seems that he tried it: He have just said: …and it worked!. – Isaac Nov 9 at 10:20
  • Yeah i tried this, thank you both! – Z E Nir Nov 9 at 10:24
  • sorry my bad, I miss-read it as a question. – ctrl-alt-delor Nov 9 at 10:25
for file in *; do
  [ -f "$file" ] || continue
  IFS= read -r line < "$file" || [ -n "$line" ] || continue
  case $line in
    ("#include"*) printf '%s\n' "$file"
  esac
done

To print the content of the file instead of its name, replace the printf command with cat < "$file".

If your awk supports the nextfile extension, and you don't care about the potential side effects of opening non-regular files:

awk '/^#include/{print substr(FILENAME, 3)}; {nextfile}' ./*

With zsh, you can replace ./* with ./*(-.) to only pass regular files (or symlinks to regular files like for the [ -f ... ] approach above) to awk.

Or to print the file contents instead of name:

awk 'FNR == 1 {found = /^#include/}; found' ./*

(that one is portable).

  • I tried this script, for some reason it does nothing, I have a file starts with '#include' and still nothing printes, If I press 'Enter' nine times i'm returning to the bash – Z E Nir Nov 9 at 9:53
  • @ZENir, in my initial version of the script, I had forgotten the < "$file". Try reloading the page. – Stéphane Chazelas Nov 9 at 10:44
for file in *
do
  [ -f "$file" ] && head -n 1 < "$file" | grep -q '^#include' && cat < "$file"
done

Beware of the fact that, with -q option enabled, grep will exit with a zero status even if an error occurred.

This question is a perfect example where bash and sed solutions are quite complex, but the task can be much simpler with (GNU) awk:

gawk 'FNR==1 && /^#include/{print FILENAME}{nextfile}' *
  • A similar solution has already been given. Yours has issues with file names that contain = characters. Note that a few other awk implementations beside GNU awk support nextfile these days. Here, because you're using FNR == 1, it would also work in implementations that don't support nextfile. – Stéphane Chazelas Nov 9 at 17:14
  • @StéphaneChazelas You're right, I read your answer too fast and didn't notice your awk examples, sorry about that – user000001 Nov 9 at 18:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.