13

In a linux shell, I want to make sure that a certain set of files all begin with <?, having that exact string and no other characters at the beginning. How can I grep or use some other to express "file begins with"?


Edit: I'm wildcarding this, and head doesn't give a filename on the same line, so when I grep it, I don't see the filname. Also, "^<?" doesn't seem to give the right results; basically I'm getting this:

$> head -1 * | grep "^<?"
<?
<?
<?
<?
<?
...

All of the files are actually good.

11 Answers 11

12

In Bash:

for file in *; do [[ "$(head -1 "$file")" =~ ^\<\? ]] || echo "$file"; done

Make sure they are files:

for file in *; do [ -f "$file" ] || continue; [[ "$(head -1 "$file")" =~ ^\<\? ]] || echo "$file"; done
3
  • and since we are all so pedantic: do not use the glob operator on huge amounts of filesnames, instead use find
    – akira
    Mar 15, 2010 at 13:01
  • using find can also return only plain files directly to start the pipe.
    – mpez0
    Mar 15, 2010 at 13:40
  • 1
    You can completely do it in Bash when using read instead of head, too: for file in *; do [ -f "$file" ] || continue; read < "$file"; [[ "$REPLY" =~ ^\<\? ]] || echo "$file"; done
    – janmoesen
    Mar 17, 2010 at 16:31
5

Do the grep:

$ head -n 1 * | grep -B1 "^<?"
==> foo <==
<?
--
==> bar <==
<?
--
==> baz <==
<?

Parse out the filenames:

$ head -n 1 * | grep -B1 "^<?" | sed -n 's/^==> \(.*\) <==$/\1/p'
foo
bar
baz
3

You can use awk for this:

$ cat test1
<?xxx>
111
222
333
$ cat test2
qqq
aaa
zzz
$ awk '/^<\?/{print "Starting with \"<?\":\t" ARGV[ARGIND]; nextfile} {print "Not starting with \"<?\":\t" ARGV[ARGIND]; nextfile}' *
Starting with "<?":     test1
Not starting with "<?": test2
$
3

Except for empty files, this Perl script seems to work:

perl -e 'while (<>) { print "$ARGV\n" unless m/^<\?/; close ARGV; }' *

I'm not immediately sure how to handle empty files; I'd be tempted to treat them as a separate special case:

find . -type f -size +0 -print0 |
    xargs -0 perl -e 'while (<>) { print "$ARGV\n" unless m/^<\?/; close ARGV; }'
2

Try this

for i in `find * | grep "php$"`; do echo -n $i " -> "; head -1 $i; done

This will get a list of every file ending in PHP, then loop thru it. echoing the file name and then printing the first line of the file. I just inserted

will give you output like:

calendar.php  -> <?php
error.php  -> <?php
events.php  -> <?php
gallery.php  ->
index.php  -> <?php
splash.php  -> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
information.php  -> <?php
location.php  -> <?php
menu.php  -> <?php
res.php  -> <?php
blah.php  -> <?php

then you can stick a normal grep at the end to get rid of what you want to see and find just exceptions

for i in `find * | grep "php$"`; do echo -n $i " -> "; head -1 $i; done | grep -v "<?php"

output:

gallery.php  ->
splash.php  -> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2
  • 4
    Useless use of grep; use "find -name '*.php'". Also, dangerous use of variables: use "find -exec your command here '{}' '+'" to avoid problems with "special" file names. Aside from that, always quote your variables: "head -1 "$i"", not "head -1 $i".
    – janmoesen
    Mar 12, 2010 at 23:04
  • for x in *.php;do echo $x \"head -n1 $x\";done
    – user23307
    Mar 13, 2010 at 0:00
1

Bash 4.0

#!/bin/bash
shopt -s globstar
for php file in /path/**/*.php
do
   exec 4<"$php";read line <&4;exec 4<&-
   case "$line" in
     "<?"*) echo "found: $php"
   esac

done
1

As you are trying to only find files that do not start with the search term, I recommend using grep with the -v option.

In this example I've used grep recursively and let it print line numbers, pipe the output to a second grep instance, which matches on line number 1 followed by your search string:

╰─$ grep -nr "." | grep -v "1:<?" 
foo/16:1:a<?
asdf/16:1:a<?
11:1:<a?
16:1:a<?

As you can see the output will consist of filepath:line:match. We can use some sed magic to just output the path to the files:

╰─$ grep -nr "." | grep -v "1:<?" | sed -E 's/(.+?):1:.*/.\/\1/'
./foo/16
./asdf/16
./11
./16
1
  • 1
    grep -nr -m1 "" . should speed up the process as it skips searching after the first match (first line).
    – mgutt
    Sep 22, 2022 at 6:51
0
cat file.txt | head -1 | grep "^<?"

should do what you're asking for.

8
  • Yeah, but if I wildcard it, it doesn't give me filenames :( Also "^<?" didn't work for me, I used the -v switch.
    – user13743
    Mar 12, 2010 at 19:27
  • 2
    @Phoshi Compulsive cat usage, head -1 file.txt | grep "^<?" is enough. Mar 12, 2010 at 19:29
  • 1
    Useless use of cat :-(((
    – vwegert
    Mar 12, 2010 at 19:32
  • Useless cat is useless :(
    – user13743
    Mar 12, 2010 at 19:34
  • I find it's much simpler to remember commands if you keep everything modular and broken down. I -know- cat will work, I don't know if command will take the file as an argument. It might not be strictly necessary, but I'm not taking it out :)
    – Phoshi
    Mar 12, 2010 at 20:03
0

this:

  % for i in *; do head -1 $i | grep "^<?" ; echo "$i : $?"; done

gives you something like this:

  foo.xml: 0
  bla.txt: 1

every file not containing your pattern will be "marked" with "1". you can play with that until it fits your needs.

2
  • 1
    You need to quote the file names if they could contain spaces. And you'd probably want to lose the output from 'grep' to /dev/null. You could also use: head -1 "$i" | grep '^<?' || echo "$i" which will only print the file name if it is problematic. Mar 12, 2010 at 22:37
  • 2
    That is what "grep -q" is for. :-)
    – janmoesen
    Mar 12, 2010 at 23:05
0

Let me have a go at this

find -type f | awk '
{
 if(getline ret < $0){
  if(ret~"^<\\?$"){
   print "Good["$0"]["ret"]";
  }else{
   print "Fail["$0"]";
  };
 }else{
  print "empty["$0"]";
 };
 close($0);
}'

nobody said wak was not available :-)

0

EDIT: as @AdamMierzwiak pointed out, this solution doesn't answer OP's question. (it match every files that has ANY line which start with <?)

grep -lG "^<?" *

explanation:

-l, --files-with-matches
    Suppress normal output; instead print the name of each input 
    file from which output would normally have been printed. The
    scanning will stop on the first match. (-l is specified by POSIX.)

see this link for command explanation : https://explainshell.com/explain?cmd=grep+-lG+%22%5E%3C%3F%22+*

2
  • I think this action will return also files that has first line different without match but match is on beginning of any line in file at least once while question author stated "having that exact string and no other characters at the beginning" Jul 24, 2022 at 8:58
  • @AdamMierzwiak you are right. I search for a solution but didn't find one. If only it was possible to disable the multiline flag.
    – julesl
    Jul 28, 2022 at 16:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .