294

Suppose I have some output from a command (such as ls -1):

a
b
c
d
e
...

I want to apply a command (say echo) to each one, in turn. E.g.

echo a
echo b
echo c
echo d
echo e
...

What's the easiest way to do that in bash?

P Shved
  • 96,026
  • 17
  • 121
  • 165
Alex Budovski
  • 17,947
  • 6
  • 53
  • 58
  • 3
    `ls -1` may be an example here but it is important to remember that it is not good to parse the output of `ls`. See: https://mywiki.wooledge.org/ParsingLs – codeforester Oct 31 '18 at 23:28

10 Answers10

323

It's probably easiest to use xargs. In your case:

ls -1 | xargs -L1 echo

The -L flag ensures the input is read properly. From the man page of xargs:

-L number
    Call utility for every number non-empty lines read. 
    A line ending with a space continues to the next non-empty line. [...]
pixatlazaki
  • 572
  • 8
  • 19
Michael Mrozek
  • 169,610
  • 28
  • 168
  • 175
  • 33
    `ls` automatically does `-1` in a pipe. – Dennis Williamson Apr 26 '10 at 03:48
  • 5
    @Dennis, doesn't look like it: `ls | xargs -L2 echo` and `ls -1 | xargs -L2 echo` give two different outputs. The former being all on one line. – Alex Budovski Apr 26 '10 at 04:03
  • 2
    @Alex: I get the same output. – Dennis Williamson Apr 26 '10 at 06:00
  • 10
    `xargs` can run only executable files not shell functions or shell built-in commands. For the former the best solution is probably the one with `read` in a loop. – pabouk - Ukraine stay strong Aug 27 '13 at 12:31
  • 1
    Depending on the file names, and the command (something that is not `echo`), you might need to say `ls -1 -Q | xargs -L1 command` –  Jun 24 '15 at 08:17
  • 1
    This would behave very badly with unusual/surprising names. Try names with spaces or quotes. – Charles Duffy May 04 '16 at 15:28
  • Perf note: the `while` solution is way faster than this one though. – math2001 Sep 13 '17 at 05:59
  • `xargs -L1` has a shortcut: `xargs -l` – gnod May 16 '18 at 13:14
  • `ls` behaves like `ls -1` and prints one file name per line when it senses that the output is not connected to a terminal, as in the case of a pipeline `ls | ...` or `ls > file`. – codeforester Oct 31 '18 at 23:30
  • 18
    I wish this answer included an explanation of what `-L1` is for. – Wyck Nov 08 '18 at 15:23
  • 9
    **-L, --max-lines=MAX-LINES** use at most MAX-LINES non-blank input lines per command line. - Meaning that xargs with -L1 will execute a command _consuming_ a single line at each time. – eja Jul 31 '19 at 09:04
  • 1
    Doesn't work with filenames that have whitespace in them, tries and runs the command on each segment of the filename. – balupton Jul 09 '21 at 03:26
  • 1
    @balupton That was pointed out five years ago. You'd probably have to use some combination of `find -print0` and `xargs -0`, but including spaces in filenames is going to break so many things that you should really just not do it – Michael Mrozek Jul 09 '21 at 03:42
  • I use xargs but didn't know -L1 was present.. it opens up a lot more options to me... thanks – Anuraag Tummanapally Jun 12 '23 at 10:40
  • Btw, this is not ideal, as `xargs` doesn’t actually call bash’s `echo` but `/usr/bin/echo`, giving the illusion of being able to call script functions, while spawning a slow new process for each call. – Evi1M4chine Aug 16 '23 at 11:54
266

You can use a basic prepend operation on each line:

ls -1 | while read line ; do echo $line ; done

Or you can pipe the output to sed for more complex operations:

ls -1 | sed 's/^\(.*\)$/echo \1/'
AdrienBrault
  • 7,747
  • 4
  • 31
  • 42
Trey Hunner
  • 10,975
  • 4
  • 55
  • 114
  • 1
    The sed command doesn't seem to work: `sh: cho: not found a sh: cho: not found` Looks like it's taking the `e` in echo to be a sed command or something. – Alex Budovski Apr 26 '10 at 03:40
  • +1 for the `while` loop. `cmd1 | while read line; do cmd2 $line; done`. Or `while read line; do cmd2 $line; done < <(cmd1)` which doesn't create a subshell. This is the simplified version of your `sed` command: `sed 's/.*/echo &/'` – Dennis Williamson Apr 26 '10 at 03:45
  • 1
    @Alex: change the double quotes to single quotes. – Dennis Williamson Apr 26 '10 at 03:47
  • Ah, it wasn't the quotes -- I just missed the `s` for substitution. But this just prints out lines like `echo foo`, etc. Not executing the `echo`. – Alex Budovski Apr 26 '10 at 03:59
  • @Alex: That is correct the lines are not executed. The while example is much more flexible and will simply execute the commands (which is why I put "echo echo ..." so echo would actually be displayed) – Trey Hunner Apr 26 '10 at 04:33
  • 8
    Quote the `"$line"` in the while loop, in order to avoid word splitting. – ignis Dec 10 '12 at 16:12
  • 5
    Try using `read -r line` to prevent `read` messing with escaped characters. For example `echo '"a \"nested\" quote"' | while read line; do echo "$line"; done` gives `"a "nested" quote"`, which has lost its escaping. If we do `echo '"a \"nested\" quote"' | while read -r line; do echo "$line"; done` we get `"a \"nested\" quote"` as expected. See http://wiki.bash-hackers.org/commands/builtin/read – Warbo Jul 07 '15 at 13:21
  • @Trey, ...look at what happens with a filename with a tab character or a run of spaces right now -- you'll see them replaced with a single space. Or a file named `-n` may simply have no output at all. Or a file named `*` could be replaced with all the other files in the directory, printing the others twice and leaving it out. `printf '%s\n' "$line"` would be much more reliable. – Charles Duffy May 04 '16 at 15:30
  • Moreover, `ls -1` isn't guaranteed to represent names accurately; names with newline literals, for instance, *simply can't* be represented accurately in a newline-delimited stream. http://mywiki.wooledge.org/ParsingLs goes into more pitfalls. – Charles Duffy May 04 '16 at 15:31
17
for s in `cmd`; do echo $s; done

If cmd has a large output:

cmd | xargs -L1 echo
Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
12

You can use a for loop:

for file in * ; do
   echo "$file"
done

Note that if the command in question accepts multiple arguments, then using xargs is almost always more efficient as it only has to spawn the utility in question once instead of multiple times.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • 1
    It's worth describing the proper/safe use of xargs, ie. `printf '%s\0' * | xargs -0 ...` -- otherwise, it's quite unsafe with filenames with whitespace, quotes, etc. – Charles Duffy May 04 '16 at 15:31
11

You actually can use sed to do it, provided it is GNU sed.

... | sed 's/match/command \0/e'

How it works:

  1. Substitute match with command match
  2. On substitution execute command
  3. Replace substituted line with command output.
Łukasz Daniluk
  • 460
  • 3
  • 9
  • Fantastic. I forgot to put the **e** command at the end, but did so after seeing your reply and it worked. I was trying to append a random ID between 1000 and 15000, when SED matches a line. `cat /logs/lfa/Modified.trace.log.20150904.pw | sed -r 's/^(.*)(\|006\|00032\|)(.*)$/echo "\1\2\3 - ID \`shuf -i 999-14999 -n 1\`"/e' ` – sgsi Sep 08 '15 at 21:37
10

A solution that works with filenames that have spaces in them, is:

ls -1 | xargs -I %s echo %s

The following is equivalent, but has a clearer divide between the precursor and what you actually want to do:

ls -1 | xargs -I %s -- echo %s

Where echo is whatever it is you want to run, and the subsequent %s is the filename.

Thanks to Chris Jester-Young's answer on a duplicate question.

balupton
  • 47,113
  • 32
  • 131
  • 182
3

xargs fails with with backslashes, quotes. It needs to be something like

ls -1 |tr \\n \\0 |xargs -0 -iTHIS echo "THIS is a file."

xargs -0 option:

-0, --null
          Input  items are terminated by a null character instead of by whitespace, and the quotes and backslash are
          not special (every character is taken literally).  Disables the end of file string, which is treated  like
          any  other argument.  Useful when input items might contain white space, quote marks, or backslashes.  The
          GNU find -print0 option produces input suitable for this mode.

ls -1 terminates the items with newline characters, so tr translates them into null characters.

This approach is about 50 times slower than iterating manually with for ... (see Michael Aaron Safyans answer) (3.55s vs. 0.066s). But for other input commands like locate, find, reading from a file (tr \\n \\0 <file) or similar, you have to work with xargs like this.

phil294
  • 10,038
  • 8
  • 65
  • 98
3

i like to use gawk for running multiple commands on a list, for instance

ls -l | gawk '{system("/path/to/cmd.sh "$1)}'

however the escaping of the escapable characters can get a little hairy.

Chris
  • 443
  • 1
  • 5
  • 13
1

Better result for me:

ls -1 | xargs -L1 -d "\n" CMD
  • Better, but not perfect. `find . -mindepth 1 -maxdepth 1 -print0 | xargs -0 command` will handle cases where the output of `ls -1` is ambiguous; use `-printf '%P\0'` rather than `-print0` if you don't want a leading `./` on each. – Charles Duffy May 04 '16 at 15:33
0

This question is a duplicate of Execute a command once per line of piped input? it seems.

I don’t know if it’s appropriate to post an adapted version of the same answer, since it’s the same question, like everyone else did. I’d prefer to merge the questions (any admin reading this?).

But until then, here we go:

What you are asking for is known as a functor. A mapping function.

Since echo isn’t a particularly sensible function to apply things to, since things that go in a pipe are already echoed without that pipe, I’ll use the custom function bla() here.

I also adapted the answer for your ls -1 case.


This should work for everything,

  • including self-defined functions (which xargs can’t do directly),
  • without spawning any additional processes (like xargs does), and
  • without removing spaces (which read otherwise does!).

Note the IFS= and -r, not included in any other answer:

mapp() { while IFS= read -r line; do "$1" "$line"; done; }

Here’s an example usage:

$ bla() { echo "  bla: $1"; }
$ ls -1 | mapp bla 
  bla: a
  bla: b
  bla: c
  …

For alternative versions and other variants, see my answer to the other question.

Evi1M4chine
  • 6,992
  • 1
  • 24
  • 18