David
David

Reputation: 1356

Weird, unexpected output in Bash Script with Perl

I'm trying to write a Bash script that will be run in a folder containing multiple C source files. One of these will contain main(), and will also contain a multiline comment at the top that describes the file/program and its features.

What I have so far is:

#! /bin/bash
echo "Grepping for main *.c file"
file=$(grep -l main *.c)
echo "the file is ${file}"
comment=$(perl -lne 'print if (/\/\*/ .. /\*\//)' ${file})
echo ${comment}

Which tells me which file contains the main() (in the test case, it's main.c). The perl function then outputs (what I think to be) incorrectly. Instead of just searching in the file, it outputs

Grepping for main *.c file
the file is main.c
/$RECYCLE.BIN /Applications /Backup /Extra /Extra (from old Mac) 2 /Library /Network /OS X Install Data /System /System Volume Information /Users /Volumes /bin /boot CMakeLists.txt build comment.sh main.c main.dSYM makefile tasksheet.pdf Student Number: n9999999 CMakeLists.txt a1_n9999999 build comment.sh main.c main.dSYM makefile tasksheet.pdf...

followed by the titles of the contents of the folder, with the actual multiline comment scattered throughout the output.

If I try and cat main.c it outputs correctly, but if in the bash script I do echo cat main.c or cat ${file} i get a similar type of garbage output. What is causing it? I have a feeling it is to do with the fact that a multiline comment contains /*, which in Unix is 'everything in the root directory', but is there a way around this?

Edit: Running that perl command perl -lne 'print if (/\/\*/ .. /\*\//)' main.c in the terminal outputs the intended result.

Upvotes: 2

Views: 166

Answers (1)

Jonathan Leffler
Jonathan Leffler

Reputation: 753695

You need to double quote the echo:

echo "${comment}"

You're seeing the expansion of the /* into the list of files in your root directory.

(And that's a lot of debris in your root directory that shouldn't be there. You shouldn't be able to write files in the / directory. You need to worry about how you are (mis)using root privileges.)


Note that the test for main is not really sensitive enough. A file containing the word remaining or any of the many other words containing the sequence of letters m-a-i-n will be matched. However, that's largely tangential to the issue at hand.

I'm not very good with grep. Any hints as to how to make the test more robust?

The most fundamental step would be grep -l -w main to search for the word 'main'. However, that would pick up 'the main idea is …' and so on, in a comment. I'd probably exploit knowledge of how you format your main() function, using grep -l -E '^int main\((void|int argc, char \*\*argv)\)$ to pick up either int main(void) or int main(int argc, char **argv) — they're the only signatures I ever use, and I keep the type on the same line as the function. If you put the int on the previous line, drop int from the match; if you have the open brace on the same line, modify the pattern to allow for, or require, that layout; etc. You might still run into a 'main in a comment' problem, but it's pretty unlikely.

I also note that the Perl will pick up each block comment in each file, rather than stopping at the end of the first one. You shouldn't have a single comment in most source files. I think you can fix that with:

perl -ne 'if (/\/\*/ .. /\*\//) { print if ($comment == 0); } ++$comment if m/\*\//;'

I tried a more compact notation:

perl -lne 'print if (/\/\*/ .. /\*\// && $comment == 0); ++$comment if m/\*\//'

and a number of variants on this theme and the && $comment == 0 condition was functionally ignored. The alternative shown above worked.


Curiosity got the better of me. Using the 'Deparse' module (SO search '[perl] deparse') and applying it shows:

$ perl -MO=Deparse -lne 'print if (/\/\*/ .. /\*\// && $comment == 0); ++$comment if m/\*\//'
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print $_ if m[/\*] .. m[\*/] && $comment == 0;
    ++$comment if m[\*/];
}
-e syntax OK
$ perl -MO=Deparse -lne 'print if ((/\/\*/ .. /\*\//) && $comment == 0); ++$comment if m/\*\//'
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print $_ if m[/\*] .. m[\*/] and $comment == 0;
    ++$comment if m[\*/];
}
-e syntax OK
$ perl -lne 'print if ((/\/\*/ .. /\*\//) && $comment == 0); ++$comment if m/\*\//'  e2big.c
/* SO 18559403: How big an argument list is allowed */
$

The precedence of && is high; that of and is low. If you're using the range operator .., be cautious and use copious parentheses in the correct places.

Upvotes: 3

Related Questions