12

Let me explain with an example:

I have a line where I declare an alias in my ~/.bashrc:

> grep lsdir ~/.bashrc

alias lsdir='ls -d */'

I have just added this line to my bashrc, and the alias is thus not yet added to my current session environment. I would also like not to re-source all my bashrc for some configuration reason, which is why I would like to simply run this grepped line on its own.

For the purpose of curiosity and education, I tried to do it without having to write it down manually, to no avail.

I tried this:

> $(grep lsdir ~/.bashrc)
bash: alias: -d: not found
bash: alias: */': not found

or this:

> "$(grep lsdir ~/.bashrc)"
bash: alias lsdir='ls -d */': No such file or directory

or even this:

> $("grep lsdir ~/.bashrc")
bash: grep lsdir ~/.bashrc: No such file or directory

But none worked. Is there any way to achieve this ?

7
  • 4
    While the answers below are better answers to your question, it's dangerous to execute code you don't actually inspect. Can I suggest creating a separate file $HOME/.bash_aliases, and put your aliases in there, and source it inside your .bashrc; then when you need to update aliases, source only that file. You could even do alias updatealiases='source $HOME/.bash_aliases'.
    – frabjous
    Mar 28, 2022 at 18:39
  • 3
    @frabjous: while I agree with what you said the code in your .bashrc is going to be executed with each new session whether you inspect it or not, and I'm sure most people aren't inspecting every line of their .bashrc that often.
    – jesse_b
    Mar 28, 2022 at 18:46
  • 4
    @jesse_b You mean I'm the only one who changes it three times a day? LOL, but in all seriousness, we're talking here about sourcing parts of your .bashrc blindly, and those parts do not necessarily work the same as they do at start up. What if in the original .bashrc there's code inside a conditional which is no longer in a conditional when pulled out with grep, or uses a variable that is defined differently in the interactive shell?
    – frabjous
    Mar 28, 2022 at 19:56
  • 1
    @frabjous: fair point
    – jesse_b
    Mar 28, 2022 at 20:00
  • 3
    This is the same issue as in How can we run a command stored in a variable?. That is, the string you get from the expansion, alias lsdir='ls -d */', gets word-split to alias, lsdir='ls, -d, */' which isn't what you want, but more like running the command line alias "lsdir='ls" -d "*/'". (And the */' gets expanded to any matching filenames if there are any, and if there aren't failglob or nullglob will trigger.)
    – ilkkachu
    Mar 29, 2022 at 13:15

4 Answers 4

23

You could use Process Substitution to source just the matching lines:

source <(grep lsdir ~/.bashrc)
1
  • I tried it and your solution works too, thanks a lot !
    – adamency
    Mar 28, 2022 at 17:41
13

I found the answer:

The key is using the builtin eval. Doing this will achieve the wanted behavior (more precisely running the command in the actual shell we're logged in, instead of a subshell):

> eval "$(grep lsdir ~/.bashrc)"
> type lsdir
lsdir is aliased to `ls -d */'
4
  • 6
    You should double-quote the command substitution (i.e. eval "$(grep lsdir ~/.bashrc)") -- without that, the command goes through word splitting and wildcard expansion before it's evaled, which can have really weird effects. Mar 28, 2022 at 19:12
  • 2
    though word splitting is a bit weird here, since eval does join any arguments it gets with spaces before running the command, so it seems it would work. But it'll fall down with something like eval $(printf 'echo foo\necho bar\n') where the newline gets turned into a space and the whole thing runs as one command, instead of two as it would if the middle newline was intact.
    – ilkkachu
    Mar 29, 2022 at 13:08
  • Thanks @ilkkachu for mentioning an actual case where double quotes are needed for it work. (I was not sure about Gordon's point as my command worked without it)
    – adamency
    Mar 30, 2022 at 17:46
  • @adamency, or anything with glob characters or multiple consecutive spaces, e.g. eval "$(printf 'echo "1 * 2"')"
    – ilkkachu
    Mar 30, 2022 at 18:31
10

While existing answers provide viable solutions, none of them explains why the original command $(grep lsdir ~/.bashrc) didn't work.

In fact, the shell does a limited amount of work on commands during substitution: you get the word splitting, but no quote handling:

pi@raspberrypi:~ $ echo 'a'
a
pi@raspberrypi:~ $ echo "echo 'a'"
echo 'a'
pi@raspberrypi:~ $ $(echo "echo 'a'")
'a'

As you can see in the last line, echo 'a' was correctly split in the command (echo) and the argument ('a'), but the argument itself didn't undergo quote processing, so quotes were kept intact. BTW, pretty much the same thing happens when you try to store a shell command in a variable.

In your case, alias lsdir='ls -d */' got split into the command, alias, its argument, lsdir='ls and two additional arguments (-d and */') that alias didn't expect.

One natural solution allowing to re-run all command processing (including quote handling) is to use eval, and one of the answers already mentions.

1
-1

you can just use a variable as the output for that

torun=$(some commands | adapting/parsing results #be aware of multiline returns too)
${torun} # call the generated command

As mentionned here due to discussion, & good idea in most cases you have to add a subcommand using a pipe for parsing/parsing the result of the command to avoid escape characters or quotting issues when appling the ${torun} variable as a command

in brief : do not forget to make the string become a usable command inside the variable definition line.

typical :

torun=$( command | sed '....;.....;....' ) #or awk or any string management tools
9
  • 3
    This doesn't parse quotes, escapes, etc (the same problem that $(grep lsdir ~/.bashrc) had). Mar 28, 2022 at 19:14
  • this only depends how you adjust the result inside the variable=$(...) command to avoid this problem adding for example a | sed patternmanagement
    – francois P
    Mar 29, 2022 at 8:16
  • 1
    No, adjusting the result won't help because the shell parses quotes and escapes (and most other shell syntax) before expanding variables, so putting quotes, escapes, etc in variables doesn't do anything useful. See "Why does shell ignore quoting characters in arguments passed to it through variables?" Mar 29, 2022 at 8:43
  • 2
    Try it with something that prints alias lsdir='ls -d */' (the same command as in the original question). You will get the same errors (bash: alias: -d: not found and bash: alias: */': not found); then run alias lsdir and you'll see a garbled mess: alias lsdir=''\''ls' Mar 29, 2022 at 10:21
  • 1
    This won't work. It's the same issue as in the question, you only get a single string from the command substitution, and passing it through a variable doesn't change that. See How can we run a command stored in a variable? for how it fails. Or just try something like echo "foo bar" vs. x=$(printf 'echo "foo bar"'); $x. Or just the case in the question.
    – ilkkachu
    Mar 29, 2022 at 13:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .