4

grep seems broken on my machine. Reinstalling it does not help. A reboot does not help. The first two lines create a file containing arbitrary text and the input is terminated with the control-D character.

ls -1 means list in one column. A example follows...

> cat > file0.txt
asdf
> cp file0.txt file1.txt
> ls -1
file0.txt
file1.txt
> ls -1 | grep f*
> 

Before experiencing this problem I did make a mistake by invoking grep with an unmatched quote but I do not understand how this problem can survive a reboot.

Unmatched quote example...

ls -1 | grep 'file* | wc

In this example the directory listing is piped to grep which pipes to the line, word, and byte counter program wc.

H2ONaCl
  • 10,039

4 Answers4

14

The expression grep f* will, in your case, be expanded to grep file0.txt file1.txt by the shell.

Apparently there are no line with file0.txt in the file file1.txt.

I know that's not what you wanted, but that how it works.

:~$ mkdir test
:~$ cd test/
:~/test$ touch f1
:~/test$ touch f2
:~/test$ set -x
:~/test$ ls | grep f* 
+ ls --color=auto
+ grep --color=auto f1 f2
Soren A
  • 7,191
11

grep did not use the output of ls in any way.

When you run grep f* in a situation where your shell expands f* to two or more arguments, grep considers all but one of them as names of files to open and read. When grep reads from named files, its default behavior of reading from standard input doesn't apply, so it doesn't read data piped to it from another command.

Your grep program is not broken, and the behavior you have observed is the correct and expected behavior, which is why reinstalling grep and rebooting did not change it. Details follow.

grep f* did not pass an argument of f* to grep.

grep never saw the text f*. As Soren A says, f* is a glob. Globs are treated specially by your shell. Because f* was not quoted, the shell expanded it into the names of files in the current directory that start with f. Each such name was passed to grep as a separate argument. (This kind of shell expansion is known variously as globbing, filename expansion, and pathname expansion.)

Based on your description, there are exactly two files that match the f* glob: file0.txt and file1.txt. Since those are the only two files in the current directory whose names start with f, the command-line arguments passed to grep from running grep f* are exactly the same as those passed to it from running grep file0.txt file1.txt. If you were to add more such files to the directory, then grep f* would pass more than those two filenames as arguments to grep, but file0.txt and file1.txt would still be included among them.

You piped ls output to grep, but grep did not read from the pipe.

A pipe connects the standard output of one command to the standard input of the other. Like cat and many other commands, grep accepts input in two ways:

  1. You can pass it filenames as command-line arguments, and it will read from those files. The first non-option argument to grep is the pattern, but subsequent non-option arguments are treated as filenames from which input will be taken.

    (You're not trying to pass multiple patterns to grep, but if you were, you could use the -e option, whose operand is always treated as a pattern.)

  2. You can pass no filename arguments, and it will read from its own standard input. This is the situation in which it is effective to pipe to grep. It is only in the absence of filename arguments that grep reads from standard input at all.

    This is why it's possible to use grep to search one or more files, without it blocking and waiting for you to enter input into a terminal. When you pipe to grep, the extra input that would make no sense for grep to use comes from the command on the left side of the pipe rather than from your terminal. grep still doesn't use it, and this is a good thing.

    (You're not trying to cause grep to read standard input in addition to one or more named files, but if you were, you could pass it the - argument.)

Because you have two files in the current directory whose names start with f, the glob f* expands to two arguments. The first, file0.txt, is used as the pattern. The second, file1.txt, is taken to name an input file. Because grep is given an argument specifying an input file, the first of the two situations described above applies ("You can pass it filenames as command-line arguments"). So grep never reads from standard input and never uses the output of ls.

Eliah Kagan
  • 119,640
0
ls | grep 'f*'

is poor form.

ls f*

should be preferred.

When using more complex regex patterns, switch to egrep/grep -E.

While I'm here, cat is seldom needed. tac is 100x more useful than cat. cat abuse is a serious issue on Unix-like systems. ;)

JohnP
  • 728
0

* is a special character which is loosely interpreted as a new command / instruction. You want to place a \ in front of it:

ls -1 | grep f\*

However what you probably want is to get all files starting with f instead of ALL the files containing an f so use another special character instead:

ls -1 | grep ^f

The ^ instructs grep to find lines starting with f.