4

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 Nov 11 '18 at 23:52

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 '18 at 10:02
  • I think unix.stackexchange.com/a/232655/117549 is reasonably close -- just anchor the pattern. – Jeff Schaller Nov 9 '18 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 Nov 10 '18 at 15:55
  • 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 Nov 10 '18 at 19:31
  • It's a fine line for me on the duplicate, since it's 99% the same idea, with this question requiring the anchor. – Jeff Schaller Nov 11 '18 at 17:26
7

It is easy to check if the first line starts with #include in (GNU and AT&T) sed:

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

Or simplified (and POSIX compatible):

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 (GNU & AT&T version):

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

Or, (BSD compatible POSIXfied version):

sed -ne '1{/^#include/!q;}' -e p  file

Or:

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 '18 at 9:58
  • @ZENir good idea, have you tried it? – ctrl-alt-delor Nov 9 '18 at 10:04
  • @ctrl-alt-delor Yes, it seems that he tried it: He have just said: …and it worked!. – Isaac Nov 9 '18 at 10:20
  • Yeah i tried this, thank you both! – Z E Nir Nov 9 '18 at 10:24
  • sorry my bad, I miss-read it as a question. – ctrl-alt-delor Nov 9 '18 at 10:25
5
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}' ./*

Above, we're adding a ./ prefix which we're stripping afterwards in FILENAME to avoid problems with file names containing = characters (or a file called -).

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 '18 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 '18 at 10:44
2
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.

0

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 '18 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 '18 at 18:29

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