How to execute the output of a command within the current shell?

93,470

Solution 1

$ ls | sed ... | source /dev/stdin

UPDATE: This works in bash 4.0, as well as tcsh, and dash (if you change source to .). Apparently this was buggy in bash 3.2. From the bash 4.0 release notes:

Fixed a bug that caused `.' to fail to read and execute commands from non-regular files such as devices or named pipes.

Solution 2

The eval command exists for this very purpose.

eval "$( ls | sed... )"

More from the bash manual:

eval

          eval [arguments]

The arguments are concatenated together into a single command, which is then read and executed, and its exit status returned as the exit status of eval. If there are no arguments or only empty arguments, the return status is zero.

Solution 3

Wow, I know this is an old question, but I've found myself with the same exact problem recently (that's how I got here).

Anyway - I don't like the source /dev/stdin answer, but I think I found a better one. It's deceptively simple actually:

echo ls -la | xargs xargs

Nice, right? Actually, this still doesn't do what you want, because if you have multiple lines it will concat them into a single command instead of running each command separately. So the solution I found is:

ls | ... | xargs -L 1 xargs

the -L 1 option means you use (at most) 1 line per command execution. Note: if your line ends with a trailing space, it will be concatenated with the next line! So make sure each line ends with a non-space.

Finally, you can do

ls | ... | xargs -L 1 xargs -t

to see what commands are executed (-t is verbose).

Hope someone reads this!

Solution 4

Try using process substitution, which replaces output of a command with a temporary file which can then be sourced:

source <(echo id)

Solution 5

`ls | sed ...`

I sort of feel like ls | sed ... | source - would be prettier, but unfortunately source doesn't understand - to mean stdin.

Share:
93,470

Related videos on Youtube

kch
Author by

kch

Updated on December 21, 2020

Comments

  • kch
    kch over 3 years

    I'm well aware of the source (aka .) utility, which will take the contents from a file and execute them within the current shell.

    Now, I'm transforming some text into shell commands, and then running them, as follows:

    $ ls | sed ... | sh
    

    ls is just a random example, the original text can be anything. sed too, just an example for transforming text. The interesting bit is sh. I pipe whatever I got to sh and it runs it.

    My problem is, that means starting a new sub shell. I'd rather have the commands run within my current shell. Like I would be able to do with source some-file, if I had the commands in a text file.

    I don't want to create a temp file because feels dirty.

    Alternatively, I'd like to start my sub shell with the exact same characteristics as my current shell.

    update

    Ok, the solutions using backtick certainly work, but I often need to do this while I'm checking and changing the output, so I'd much prefer if there was a way to pipe the result into something in the end.

    sad update

    Ah, the /dev/stdin thing looked so pretty, but, in a more complex case, it didn't work.

    So, I have this:

    find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin
    

    Which ensures all .doc files have their extension lowercased.

    And which incidentally, can be handled with xargs, but that's besides the point.

    find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv
    

    So, when I run the former, it'll exit right away, nothing happens.

    • Kaleb Pederson
      Kaleb Pederson almost 15 years
      Does your complex command work when you pipe to a temp file first and then source it? If not, what's the problem with the generated output? The output of your command won't work if your filenames have spaces in them or if certain sequences aren't escaped properly. I'd want to add quotes around $1 and $2.doc at a minimum.
    • nos
      nos almost 15 years
      Is there any good reason for having to run this in the original shell ? - these examples doesn't manipulate the current shell so you gain nothing by doing so. The quick solution is you redirect output to a file and source that file though
    • kch
      kch almost 15 years
      @kaleb the output runs fine. in this particular case, even if i pipe to sh. the file names are space-safe, but thanks for noting. @nos git environment variables on the original shell. and again, these are just examples. the question is for life.
    • srcerer
      srcerer over 9 years
      source /dev/stdin didn't work for me when needing assigned variables to stick around. geirha on freenode bash pointed me to mywiki.wooledge.org/BashFAQ/024 and suggested I try a process substitution source <(command) which worked for me
  • chaos
    chaos almost 15 years
    He mentioned that temp files are icky.
  • kch
    kch almost 15 years
    after seeing mark4o's answer, doesn't it feel like it was right in our faces all this time?
  • chaos
    chaos almost 15 years
    Heh, yeah. I never remember that that stuff exists.
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten almost 15 years
    If you're using bash the $( ) syntax might be preferred. 'Course backticks work in more shells...
  • pixelbeat
    pixelbeat almost 15 years
    $(command) and $((1+1)) work in all posix shells. I think many are put off by vim marking them as syntax errors, but that's just because vim is highlighting for the original Bourne shell which very few use. To get vim to highlight correctly put this in your .vimrc: let g:is_posix = 1
  • Tanktalus
    Tanktalus over 14 years
    The only issue here is that you may need to insert ;'s to separate your commands. I use this method myself to work on AIX, Sun, HP, and Linux.
  • Milan Babuškov
    Milan Babuškov about 13 years
    Tanktalus, thanks for that comment, it just made my script work. On my machine eval does not separate commands on newlines and using source does not work even with semi-colons. Eval with semi-colons is the solution. I wish I could give you some points.
  • chriv
    chriv over 11 years
    I thought this was ingenious until I tried it in msys/mingw (where there is no /dev folder (or even devices)! I tried many combinations of eval, $(), and backticks ``. I couldn't make anything work, so I finally just redirected output into a temp file, sourced the temp file, and the removed it. Basically "sed ... > /tmp/$$.tmp && . /tmp/$$.tmp && rm /tmp/$$.tmp". Anybody got a msys/mingw solution without temp files???
  • Phil
    Phil over 11 years
    @MilanBabuškov: I ranked him up for you ;-)
  • Phil
    Phil over 11 years
    Just a remark: This one does not seem to handle export statements as expected. If the piped string has an export statement, the exportet variable is not available in your terminal environment afterwards, whereas with eval export also works perfectly.
  • Zachary Murray
    Zachary Murray over 10 years
    This is excellent. However, for my use case, I ended up having to put quotes around my $(), like so: eval "$( ssh remote-host 'cat ~/.bash_profile' )" Note that I am indeed using bash 3.2.
  • Dan Tenenbaum
    Dan Tenenbaum over 9 years
    This works for me where the accepted answer did not.
  • that other guy
    that other guy over 9 years
    In bash without the lastpipe option set, this creates a subshell just like | bash would
  • Alex Dupuy
    Alex Dupuy almost 9 years
    Given that MacOS X usually has bash 3.2 (maybe Yosemite has 4.0?), and the need for lastpipe trickery in my use case (exporting all the variables in a file by pre-processing each line with sed to add 'export ') I chose to go with the eval "$(sed ...)" approach - but thanks for the 3.2 bug heads-up!
  • shrx
    shrx almost 9 years
    @AlexDupuy nope, Yosemite still provides the old 3.2.57(1)-release (x86_64-apple-darwin14).
  • paulotorrens
    paulotorrens over 8 years
    Which kind of sorcery is this?
  • netdigger
    netdigger over 8 years
    This was my first thought as well. Though I like the eval solution better. @PauloTorrens <(xyz) simply executes xyz and replaces <(xyz) with the name of a file that will write the output of xyz. It's really easy to understand how this works by doing for example: echo <(echo id), giving the output /dev/fd/12 (12 is an example), cat <(echo id), giving the outputid, and then source <(echo id) giving the same output as simply writing id
  • Mark Stosberg
    Mark Stosberg about 8 years
    @PauloTorrens, this is called process substitution. See linked docs for official explanation, but the short answer is that "<()" is a special syntax that was designed for cases like this in mind.
  • paulotorrens
    paulotorrens about 8 years
    @MarkStosberg, do you know if this is a special syntax, or just a subshell (...) redirected?
  • Mark Stosberg
    Mark Stosberg about 8 years
    @PauloTorrens, It is a special syntax just for process substitution.
  • nhed
    nhed almost 8 years
    The problem with this answer is that the source is executed in a subshell and thus the values are lost at the invoking shell this variation tho worked for me source <(sed ...) ... but now I see that it was already given here stackoverflow.com/a/22233248/652904 by @rishta
  • William H. Hooper
    William H. Hooper over 4 years
    or use backticks: . /dev/stdin <<< `grep '^alias' ~/.profile`
  • JP de la Torre
    JP de la Torre about 2 years
    I'm from the future and this is still the best solution. Clear, short, simple and free of side effects. Specially now that backticks are considered obsolete and anti-pattern.