34

I have ran into a problem trying to write a Bash script. When grep outputs, it returns (usually) many lines. I would like to prefix and suffix a string to each of these output lines.

I would also like to note that I'm piping ls into grep, like:

ls | grep
2
  • 2
    You shouldn't parse ls - it's bad juju. Also, does it have to be grep? And can you give some example output?
    – Sobrique
    Commented Dec 24, 2015 at 19:50
  • 1
    What do you want your script to do if the filename contains a newline? Commented Dec 24, 2015 at 20:57

6 Answers 6

56

With sed:

ls | grep txt | sed 's/.*/prefix&suffix/'
3
  • 4
    Or, sed being a superset of grep: ls | sed -n '/txt/s/.*/prefix&suffix/p' or ls | sed -n 's/.*txt.*/prefix&suffix/p' Commented Dec 24, 2015 at 22:44
  • @StéphaneChazelas: Thank you for this hint.
    – Cyrus
    Commented Dec 24, 2015 at 23:22
  • If you see unexpected results, check for LF newline encoding. With CR/LF your sed, or any tool for that matter, might output some garbage. Commented Feb 1 at 14:38
13

With sed:

ls | grep pattern | sed -e 's/^/prefix/' -e 's/$/suffix/'

But note, this suffers from problems with filenames with line feeds, and all the assorted problems of parsing ls which is generally a bad idea.

With perl

perl -e 'print "prefix".$_."suffix\n" for grep { m/somepattern/} glob "*"'

Note - perl grep can take a pattern - like the grep command, but you can also do things like apply file tests.

E.g.

grep {-d} glob "*" #filters directories.
grep { (stat)[9] > time() - 60 } glob "*" #reads file mtime and compares. 

Within grep the default iterator $_ is set to the current element value, so you can apply sed/grep style regex, or perform a variety of tasks based on $_.

2
  • How would I include a variable as a suffix/prefix in your first example using ´sed´? Commented Dec 24, 2015 at 23:35
  • Change the quotes to and just use em.
    – Sobrique
    Commented Dec 25, 2015 at 9:55
4

In this case GNU find lets you do all these things in one go, eliminating pipes and potentially troublesome parsing of ls:

find . -maxdepth 1 -name '[^.]*' \
  -regextype posix-extended -regex "MYPATTERN" \
  -printf 'SOMEPREFIX %f SOMESUFFIX\n'

(find is not a good way to arbitrarily modify the output of other commands, of course!)

Some notes:

  • -maxdepth 1 and -name [^.]* make name matching work the same as a plain ls (or ls .). You can use any shell-style glob, but note that "*" will match a leading "." in a name unlike bash, so [^.]* means anything that doesn't have a leading "."
  • MYPATTERN is a proper POSIX ERE (default type is Emacs, see here), but it must match the entire filename (including the ./ prefix) so use something like .*thing.* instead of just thing
  • you can probably just use one of -name/-regex instead of both (e.g. -regex "[^.].MYPATTERN."
  • -printf supports lots of things, %n is the unadorned file or directory name
  • beware find (contrary to ls) doesn't sort the list of file names.
  • it assumes the file names constitute valid text in the current locale.

(this can depend on your version of find though, check the "STANDARDS CONFORMANCE" section of your man page)

As a possible alternative with no external programs required, bash has compgen which, among other things, expands globs, equivalent to an ls with no options:

compgen -P "someprefix>" -S "<somesuffix" -G "pattern"

This handles filenames with whitespace, including newlines. compgen -G "*" should provide the same output as a plain ls (but note that ls * is a different thing entirely). You'll still need grep, and this may or may not solve the problem, but it's worth a mention.

3

Maybe you can just use globs here. With the extglob option enabled (which enables ksh88 globs), bash globs are functionally equivalent to regexps so you don't need grep:

printf 'prefix%ssuffix\n' *foo*

For all the filenames that contain foo (equivalent to using grep foo with the extra benefit that it works properly if filenames contain newline characters).

shopt -s extglob
printf 'prefix%ssuffix\n' foo+([[:digit:]]).jpg

(as equivalent to grep -E '^foo[[:digit:]]+\.jpg$')

0

With awk

ls | grep | awk '$0="prefix"$0"suffix"'

"prefix" and "suffix" are string literals, so replace the text with whatever you want.


Sources: https://stackoverflow.com/a/2099492/2089675

-1

sed and find solutions are challenged if your prefix/suffix include regex key characters or if want to pass an environment variable (instead of an immediate value), xargs+echo might be more appropriate here.

With environment variable:

ls | grep p | xargs -I % sh -c "echo $PATH/%"

With an inline:

ls | grep p | xargs -I % sh -c "echo `pwd`/%"
4
  • The names of the files (and the contents of $PATH and the output of pwd) end up interpreted as shell code! So that makes it a code injection vulnerability. Commented Mar 24, 2021 at 17:20
  • $PATH is the search path for executables, you may be confusing with $PWD. Commented Mar 24, 2021 at 17:20
  • xargs comes with its own problems. It will choke on quotes, backslashes and leading white space. Commented Mar 24, 2021 at 17:21
  • echo also can't be used to output arbitrary data. Commented Mar 24, 2021 at 17:22

You must log in to answer this question.

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