546

In a given shell, normally I'd set a variable or variables and then run a command. Recently I learned about the concept of prepending a variable definition to a command:

FOO=bar somecommand someargs

This works... kind of. It doesn't work when you're changing a LC_* variable (which seems to affect the command, but not its arguments, for example, [a-z] char ranges) or when piping output to another command thusly:

FOO=bar somecommand someargs | somecommand2  # somecommand2 is unaware of FOO

I can prepend somecommand2 with FOO=bar as well, which works, but which adds unwanted duplication, and it doesn't help with arguments that are interpreted depending on the variable (for example, [a-z]).

So, what's a good way to do this on a single line?

I'm thinking something on the order of:

FOO=bar (somecommand someargs | somecommand2)  # Doesn't actually work

I got lots of good answers! The goal is to keep this a one-liner, preferably without using export. The method using a call to Bash was best overall, though the parenthetical version with export in it was a little more compact. The method of using redirection rather than a pipe is interesting as well.

Jamiu S.
  • 5,257
  • 5
  • 12
  • 34
MartyMacGyver
  • 9,483
  • 11
  • 47
  • 67
  • 2
    `(T=$(date) echo $T)` will work – vp_arth Nov 03 '15 at 12:28
  • In the context of cross-platform (incl. windows) scripts or npm-based projects (js or else), you might want to take a look at the [cross-env module](https://www.npmjs.com/package/cross-env). – Frank N Sep 19 '17 at 07:34
  • 11
    I was hoping one of the answers would also explain *why* this only sort of works, i.e. why it's not equivalent to exporting the variable before the call. – Brecht Machiels Oct 18 '17 at 11:03
  • 5
    The why is explained here: https://stackoverflow.com/questions/13998075/setting-environment-variable-for-one-program-call-in-bash-using-env – Brecht Machiels Oct 18 '17 at 15:56
  • Why don't you want to use `export` provided that a side effect is not to pollute your environment? In other words, why does it matter if `export` is used if the variable is not set after the command(s) finish executing. eg: In a subshell. – FreelanceConsultant Nov 24 '22 at 12:23
  • In other words: `(export foo="hello")`, `echo $foo` prints a blank line, showing that `foo` is not set outside of the subshell. – FreelanceConsultant Nov 24 '22 at 12:24

6 Answers6

466
FOO=bar bash -c 'somecommand someargs | somecommand2'
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 3
    This satisfies my criteria (one-liner without needing "export")... I take it there's no way to do this *without* calling "bash -c" (e.g., creative use of parentheses)? – MartyMacGyver Jun 01 '12 at 19:44
  • 2
    @MartyMacGyver: None that I can think of. It won't work with curly braces either. – Dennis Williamson Jun 01 '12 at 19:45
  • 8
    Note that if you need to run your `somecommand` as sudo, you need to pass sudo the `-E` flag to pass though variables. Because variables can introduce vulnerabilities. http://stackoverflow.com/a/8633575/1695680 – ThorSummoner Mar 24 '15 at 20:17
  • 17
    Note that if your command already has two levels of quotes then this method becomes extremely unsatisfactory because of quote hell. In that situation exporting in subshell is much better. – Pushpendre Jan 22 '16 at 19:07
  • 1
    Odd: on OSX, ```FOO_X=foox bash -c 'echo $FOO_X'``` works as expected but with specific var names it fails: ```DYLD_X=foox bash -c 'echo $DYLD_X'``` echos blank. both work using ```eval``` instead of ```bash -c``` – mwag Apr 24 '19 at 22:49
  • @mwag: I don't know anything about [`dyld`](https://en.wikipedia.org/wiki/Dynamic_linker#macOS_and_iOS) ("the dynamic linker"), but it evidently watches for variables that start with `DYLD_` and it recognizes a certain set of them but complains about ones it doesn't know about. The "fails" you refer to is specifically a warning message output by `dyld`: "dyld: warning, unknown environment variable: DYLD_X". Note that that is a warning, not an error, and the value of the variable is actually output. – Dennis Williamson Apr 25 '19 at 02:24
  • @DennisWilliamson: I am not invoking ```dyld```, I am invoking ```echo```, and on my system, it does fail: running the exact, verbatim command ```DYLD_X=foox bash -c 'echo $DYLD_X'```, nothing at all gets echo'd (but it does if I use ```eval``` instead of ```bash -c```) – mwag Apr 25 '19 at 02:54
  • @mwag: I wasn't invoking `dyld` either. It seems to automagically get invoked. What version of MacOS are you running and what does `bash --version` say? I just checked and the result that I reported above is because I was using Bash 5. When I use `/bin/bash` (Bash 3.2), I get no warning and a blank line is echoed (as if the variable were empty). The dynamic loader is doing something behind the scenes regardless. – Dennis Williamson Apr 25 '19 at 12:53
  • I have asked a [question](https://apple.stackexchange.com/q/358666/48577) about this on apple.stackexchange.com. – Dennis Williamson Apr 25 '19 at 13:05
  • @DennisWilliamson, thanks for noticing that and asking in the other forum. When I run on 3.2.57(1)-release (x86_64-apple-darwin17) I get a blank, and when I run on 3.2.57(1)-release (x86_64-apple-darwin14) I get the warning + variable. – mwag Apr 25 '19 at 18:50
319

How about exporting the variable, but only inside the subshell?:

(export FOO=bar && somecommand someargs | somecommand2)

Keith has a point, to unconditionally execute the commands, do this:

(export FOO=bar; somecommand someargs | somecommand2)
starball
  • 20,030
  • 7
  • 43
  • 238
0xC0000022L
  • 20,597
  • 9
  • 86
  • 152
  • This appears to work, though it's not quite what I was looking for (it may be that there is no way to do what I'm describing above in one command without explicitly using export first...) Thoughts? – MartyMacGyver Jun 01 '12 at 19:33
  • 4
    @MartyMacGyver: `&&` executes the left command, then executes the right command only if the left command succeeded. `;` executes both commands unconditionally. The Windows batch (`cmd.exe`) equivalent of `;` is `&`. – Keith Thompson Jun 01 '12 at 20:37
  • 2
    In zsh I don't seem to need the export for this version: `(FOO=XXX ; echo FOO=$FOO) ; echo FOO=$FOO` yields `FOO=XXX\nFOO=\n`. – rampion Apr 16 '13 at 19:45
  • If you have a ton of environment variables and want to load them via a file, you can do that similarly to the above by adding all your `export FOO=bar` settings into a file, then running the above like so: ``(`cat heroku_prod_config` && command some args)`` – Craig Monson Jan 07 '14 at 21:23
  • 4
    @PopePoopinpants: why not use `source` (aka `.`) in that case? Also, the backticks shouldn't be used anymore these days and this is one of the reasons why, using `$(command)` is waaaaay safer. – 0xC0000022L Jan 08 '14 at 01:04
  • 6
    So simple, yet so elegant. And I like your answer better than the accepted answer, as it will start a sub shell equal to my current one (which may not be `bash` but could be something else, e.g. `dash`) and I don't run into any trouble if I must use quotes within the command args (`someargs`). – Mecki May 01 '14 at 20:18
  • I have a bash script with a bunch of environment variables exported ie: `export DB_PORT=3306` - I start my program including the env variables like: `source ./env.sh; ./some-program` – danecando Jun 09 '16 at 02:05
  • This is the only answer that works for evaluating the same variables in the command. For example, `(export FOO=foo && echo "$FOO/bar/baz/$FOO")`. – weberc2 Apr 13 '17 at 22:11
  • One circumstance in which a variable assignment would fail and you might not want the command to be executed is if the variable was previously marked read-only. – Dennis Williamson Nov 25 '18 at 17:59
  • I encountered this error (and fix) in `zsh`: https://unix.stackexchange.com/questions/208607/zsh-export-not-valid-in-this-context – jsejcksn Jun 20 '20 at 03:39
  • 1
    If you want to set multiple env, you can run `(export FOO1='bar1'; export FOO2='bar2'; command...)` – Honghao Z Oct 18 '20 at 08:04
  • @rampion you sure? `echo` after all, is likely the shell builtin, even in ZSh. So in order to set an actual environment variable you'd still have to export it into the environment. Otherwise it remains a shell variable of some scope (here likely global), but _child processes_ won't see it (terms and conditions apply when we're talking about *subshells*, though ;)). – 0xC0000022L Jun 05 '23 at 12:43
75

Use env.

For example, env FOO=BAR command. Note that the environment variables will be restored/unchanged again when command finishes executing.

Just be careful about about shell substitution happening, i.e. if you want to reference $FOO explicitly on the same command line, you may need to escape it so that your shell interpreter doesn't perform the substitution before it runs env.

$ export FOO=BAR
$ env FOO=FUBAR bash -c 'echo $FOO'
FUBAR
$ echo $FOO
BAR
benjimin
  • 4,043
  • 29
  • 48
61

You can also use eval:

FOO=bar eval 'somecommand someargs | somecommand2'

Since this answer with eval doesn't seem to please everyone, let me clarify something: when used as written, with the single quotes, it is perfectly safe. It is good as it will not launch an external process (like the accepted answer) nor will it execute the commands in an extra subshell (like the other answer).

As we get a few regular views, it's probably good to give an alternative to eval that will please everyone, and has all the benefits (and perhaps even more!) of this quick eval “trick”. Just use a function! Define a function with all your commands:

mypipe() {
    somecommand someargs | somecommand2
}

and execute it with your environment variables like this:

FOO=bar mypipe
Teemu Leisti
  • 3,750
  • 2
  • 30
  • 39
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • 8
    @Alfe: Did you also downvote the accepted answer? because it exhibits the same “problems” as `eval`. – gniourf_gniourf Jan 28 '16 at 12:51
  • 12
    @Alfe: unfortunately I don't agree with your critique. This command is perfectly safe. You really sound like a guy who once read _`eval` is evil_ without understanding what's evil about `eval`. And maybe you're not really understanding this answer after all (and really there's nothing wrong with it). On the same level: would you say that `ls` is bad because `for file in $(ls)` is ,bad? (and yeah, you didn't downvote the accepted answer, and you didn't leave a comment either). SO is such a weird and absurd place sometimes. – gniourf_gniourf Jan 29 '16 at 08:43
  • 10
    @Alfe: when I say _You really sound like a guy who once read `eval` is evil without understanding what's evil about `eval`,_ I'm referring to your sentence: _This answer lacks all the warnings and explanations necessary when talking about `eval`._ `eval` is not bad or dangerous; no more than `bash -c`. – gniourf_gniourf Jan 29 '16 at 08:54
  • 1
    Votes aside, the comment provided @Alfe does somehow imply that the accepted answer is somehow safer. What would have been more helpful would have been for you to describe what you believe to be unsafe about the usage of `eval`. In the answer provided the args have been single quoted protecting from variable expansion, so I see no problem with the answer. – Brett Ryan Jun 27 '16 at 05:42
  • 1
    I removed my comments to concentrate my concern in one new comment: `eval` is a security issue in general (like `bash -c` but less obvious), so the dangers should be mentioned in an answer proposing its use. Careless users may take the answer (`FOO=bar eval …`) and apply it to their situation so that it raises problems. But it obviously was more important to the answerer to figure out whether I downvoted his and/or other answers than to improve anything. As I wrote before, fairness shouldn't be the main concern; being *no worse* than any other given answer also is irregardless. – Alfe Jun 27 '16 at 12:39
  • A link to http://stackoverflow.com/questions/17529220/why-should-eval-be-avoided-in-bash-and-what-should-i-use-instead would be enough to hint on the dangers. A short statement which aspects of the answer make it safe (single quotes, so don't use this without them! or similar) would be even better. – Alfe Jun 27 '16 at 12:56
  • What I do when someone tells me that my answer lacks some aspect, I don't get at him personally for wanting to know whether they downvoted my answer and whether they also downvoted other answers. Instead I try to improve my answer. But we obviously have different approaches on how to handle things. I find that avoiding communication and editing other people's answers myself is the second best solution because often the original author doesn't even notice this, so I don't know if they agree. I prefer finding a consensus even if this means to step into the pit. It just doesn't always work. – Alfe Jun 27 '16 at 13:36
  • 1
    I think the function solution is clearer, though somewhat more verbose, than the `eval` solution. – Derek Mahar Aug 23 '21 at 18:43
  • `eval` is a very powerful tool. Using it to solve the question here is like using a jackhammer when you need a screwdriver. – cambunctious Jun 06 '23 at 13:54
  • @cambunctious lol, can you please explain what's wrong with `eval`? why is it a jackhammer in this case? why is it worse than the accepted answer that uses `bash -c` (which, is actually worse than `eval` in this case as it spawns a whole new process altogether and exposes exactly the same (supposed) problems as `eval`)? I'm curious to see some genuine arguments beyond “I once read `eval` is evil, and was told to never use it”. – gniourf_gniourf Jun 07 '23 at 04:28
  • @gniourf_gniourf eval is a very powerful tool in general since it can take any string from anywhere and execute it. Strictly using eval with a single-quoted argument does seem to limit its power/risk, but that is kinda beside the point. `bash -c` also takes any string so indeed it's not much better. To me a subshell or a function is ideal here. I generally dislike writing code in a string literal, but I suppose that is purely aesthetic. – cambunctious Jun 08 '23 at 13:11
  • @cambunctious there's no user input or anything passed to eval, so your point is really moot here, and I also gave a solution using a function. – gniourf_gniourf Jun 08 '23 at 19:44
26

A simple approach is to make use of ;

For example:

ENV=prod; ansible-playbook -i inventories/$ENV --extra-vars "env=$ENV"  deauthorize_users.yml --check

command1; command2 executes command2 after executing command1, sequentially. It does not matter whether the commands were successful or not.

Akhil
  • 912
  • 12
  • 23
  • 7
    It works because it defines `ENV` in the environment of the same shell in which the commands that follow the semicolon execute. How this differs from the other answers, though, is that this one defines `ENV` for _all_ subsequent references in the shell and not just those on the same line. I believe that the original question intended to alter the environment only for the references on the same line. – Derek Mahar Aug 23 '21 at 18:34
  • 1
    adding `;unset ENV` to the same line will make it one liner. but I ignored it as it doesn't make sense. – Akhil Jan 18 '22 at 17:04
-5

Use a shell script:

#!/bin/bash
# myscript
FOO=bar
somecommand someargs | somecommand2

> ./myscript
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Spencer Rathbun
  • 14,510
  • 6
  • 54
  • 73
  • 21
    You still need `export`; otherwise `$FOO` will be a shell variable, not an environment variable, and therefore not visible to `somecommand` or `somecommand2`. – Keith Thompson Jun 01 '12 at 19:41
  • 1
    It'd work but it defeats the purpose of having a one-line command (I'm trying to learn more creative ways to avoid multi-liners and/or scripts for relatively simple cases). And what @Keith said, though at least the export would stay scoped to the script. – MartyMacGyver Jun 01 '12 at 19:47
  • @KeithThompson Your comment had something new to teach me. I never thought of shell vs environment variables. I used to think of global vs local environment variables. Now I know the correct terminology. – Alireza Mohamadi May 09 '22 at 19:50