1

I have been experimenting with some bash scripting and ran into a problem that I do not quite understand. I am trying to print out all my files in a directory and use the print out to show not only the name of the file, but also the size and last modified date. I can do that with the following command:

ls | xargs -I '{}' sh -c 'echo "{}" $(stat -c "%y %s" "{}")'

I got the the use of sh -c from this post: xargs with multiple commands (first answer). I understand that the sh -c part is the command that's being executed, which reads in the string as a command. However, what I don't quite understand is why other commands won't work, such as eval in place of sh -c. For example:

ls | xargs -I '{}' eval 'echo "{}" $(stat -c "%y %s' "{}")'

Doesn't eval also take a string as an argument and then evaluate it? (eval command in bash) I get that I have a working command with the sh -c, but I would like to know why this command is different from eval.

Community
  • 1
  • 1
Zhouster
  • 746
  • 3
  • 13
  • 23
  • 4
    `eval` is a shell built-in command; `xargs` expects an executable file. – chepner Jul 29 '14 at 16:47
  • Is that to say that when I use `sh -c`, it creates an executable file that is then passed to `xargs`? – Zhouster Jul 29 '14 at 16:48
  • 1
    The simple difference is that `sh -c 'stuff'` spawns a new process, does stuff, then the process goes away: `eval 'stuff'` does stuff in your *current shell*. – glenn jackman Jul 29 '14 at 16:48
  • `sh` *is* an executable file, which executes the string passed by the `-c` argument as a shell command. – chepner Jul 29 '14 at 16:49
  • 2
    ...to reinforce @anubhava's answer -- see http://mywiki.wooledge.org/ParsingLs for some descriptions of why parsing `ls` output is inherently prone to breakage. – Charles Duffy Jul 29 '14 at 16:50
  • So if I understand correctly, because `sh` spawns in a new command shell, `xargs` will accept it as an argument? In the man page (http://unixhelp.ed.ac.uk/CGI/man-cgi?xargs), it states that the command `builds and executes command line from standard input`. Does this mean that it `eval` doesn't count as "standard input"? – Zhouster Jul 29 '14 at 16:58
  • @CharlesDuffy: thanks for the link. I will keep that in mind in the future. As an added note, if I were to use `grep` in the pipe after the `ls` command, would that make things any better? – Zhouster Jul 29 '14 at 16:58
  • Even using `ls | grep "foobar"` is error prone for the same reason. – anubhava Jul 29 '14 at 17:15

3 Answers3

4

You don't really need eval or sh -c or ls parsing. Just use stat:

stat -c "%n %y %s" *
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Thanks for the answer. It does remind me that I should look thoroughly through the different formatting options. However, it would be great if you could shed light on the difference between `sh -c` and `eval`. – Zhouster Jul 29 '14 at 16:47
  • 3
    @Zhouster See my comment to your question, but this is the code you want to use. Piping from `ls` and using `eval` are both signs that you are Doing It Wrong. – chepner Jul 29 '14 at 16:49
  • 1
    `eval` is a shell bulletin and `sh -c` lets you run a command line in a forked sub process however effectively both do the same things and both are risky if your command line is coming from not trusted source. – anubhava Jul 29 '14 at 16:49
  • 1
    If this is for scripted use, better to use a format string like `%Y %s %n\0` -- that way once you read the mtime and the size, anything up to the next NUL character is part of the name (with no room for ambiguity, since NULs -- unlike spaces or newlines -- can't exist in names). – Charles Duffy Jul 29 '14 at 16:54
2

xargs can only run an external executable -- something which the operating system's execv() family of calls can execute. (Keep in mind that xargs itself is not part of your shell but an external program, and thus it has no access to the shell that started it).

The shell builtin eval is not an executable. Thus, xargs cannot run it. (By its nature, it invokes code in the current shell. xargs is not a shell, so it has no ability to interpret shell scripts, and there is no "current shell" when xargs is the active process).

sh is an executable (starting a new shell). Thus, xargs can start it, and it can be used in contexts where no current shell exists.


By the way -- you tagged this question bash, but sh is not bash. If you want to be able to run bash code under your shell invoked as a separate process, use bash -c, not sh -c; since sh -c starts a separate shell out-of-process, you can also get a different shell, at a different version, with different capabilities (and even if your /bin/sh is a symlink to /bin/bash, bash turns off some functionality when started as sh).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
2

sh -c summons a new shell while eval executes it on the current shell. Any changes in the environment inside the new shell does not affect the shell that called it.

konsolebox
  • 72,135
  • 12
  • 99
  • 105