2

I'm working with Mac OS X's pbpaste command, which returns the clipboard's contents. I'd like to create a shell script that executes each line returned by pbpaste as a separate bash command. For example, let's say that the clipboard's contents consists of the following lines of text:

echo 1234 >~/a.txt
echo 5678 >~/b.txt

I would like a shell script that executes each of those lines, creating the two files a.txt and b.txt in my home folder. After a fair amount of searching and trial and error, I've gotten to the point where I'm able to assign individual lines of text to a variable in a while loop with the following construct:

pbpaste | egrep -o [^$]+ | while read l; do echo $l; done

which sends the following to standard out, as expected:

echo 1234 >~/a.txt
echo 5678 >~/b.txt

Instead of simply echoing each line of text, I then try to execute them with the following construct:

pbpaste | egrep -o [^$]+ | while read l; do $l; done

I thought that this would execute each line (thus creating two text files a.txt and b.txt in my home folder). Instead, the first term (echo) seems to be interpreted as the command, and the remaining terms (nnnn >~/...) seem to get lumped together as if they were a single parameter, resulting in the following being sent to standard out without any files being created:

1234 >~/a.txt
5678 >~/b.txt

I would be grateful for any help in understanding why my construct isn't working and what changes might get it to work.

scolfax
  • 710
  • 2
  • 6
  • 17

1 Answers1

2

[…] the remaining terms (nnnn >~/...) seem to get lumped together as if they were a single parameter, […]

Not exactly. The line actually gets split on whitespace (or whatever $IFS specifies), but the problem is that the redirection operator > cannot be taken from a shell variable. For example, this snippet:

gt='>'
echo $gt foo.txt

will print > foo.txt, rather than printing a newline to foo.txt.

And you'll have similar problems with various other shell metacharacters, such as quotation marks.

What you need is the eval builtin, which takes a string, parses it as a shell command, and runs it:

pbpaste | egrep -o [^$]+ | while IFS= read -r LINE; do eval "$LINE"; done

(The IFS= and -r and the double-quotes around $LINE are all to prevent any other processing besides the processing performed by eval, so that e.g. whitespace inside quotation marks will be preserved.)

Another possibility, depending on the details of what you need, is simply to pipe the commands into a new instance of Bash:

pbpaste | egrep -o [^$]+ | bash

Edited to add: For that matter, it occurs to me that you can pass everything to eval in a single batch; just as you can (per your comment) write pbpaste | bash, you can also write eval "$(pbpaste)". That will support multiline while-loops and so on, while still running in the current shell (useful if you want it to be able to reference shell parameters, to set environment variables, etc., etc.).

ruakh
  • 175,680
  • 26
  • 273
  • 307
  • +1 for an actual appropriate use of `eval`. Also, note that the pipe-to-bash option will allow multiline structures (e.g. the normal format for if and while statements), but the line-by-line option won't. – Gordon Davisson Jan 31 '13 at 23:29
  • Wow, ruakh, thank you for that beautiful explanation and for the solutions! Following your and Gordon's lead, I kept playing with the pipe-to-bash option and found that I could eliminate `egrep` and `while` altogether. The following efficient construct handles even fairly complex multiline code (`while` loop, condition testing, variable incrementation, ...) perfectly: `pbpaste | bash`. – scolfax Feb 01 '13 at 00:07