How do I set variables from verbatim shell output? - shell

I thought this should be simple, but now that I'm trying it I can't figure it out. Did I take stupid pills this morning?
I have a command with output that is some variables that I want set. I thought I could use eval, but that apparently doesn't work.
Here's what I want to do:
$ ./foo
FOO=bar
BAR=baz
$ eval ./foo
$ echo $FOO
bar
How do I set those directly?

You need to evaluate the output of the script, not the string ./foo
$ eval $( ./foo )

What you try todo is sourcing the foo file into your shell. You can do that by invoking dot command and give your file as argument (the file does not have to be executable)
$ . ./foo
$ echo $FOO
bar

Related

Ignore "$" at the beginning of the command in Bash

On many websites, "$" is written at the beginning when introducing the Linux command.
But of course, this will result in a "$: command not found" error.
To avoid this it is necessary to delete or replace "$" every time, but it is troublesome.
So, if the beginning of the input command is "$", I think that it would be good if I could ignore "$", is it possible?
If you really need this, you can create a file in a directory that is in your $PATH. The file will be named $ and will contain
#!/bin/bash
exec "$#"
Make it executable, then you can do
$ echo foo bar
foo bar
$ $ echo foo bar
foo bar
$ $ $ echo foo bar
foo bar
$ $ $ $ echo foo bar
foo bar
Note that this does not affect variable expansion in any way. It only interprets a standalone $ as the first word in the command line as a valid command.
I just noticed a problem with this: It works for calling commands, but not for shell-specific constructs:
$ foo=bar
$ echo $foo
bar
$ $ foo=qux
/home/jackman/bin/$: line 2: exec: foo=qux: not found
and
$ { echo hello; }
hello
$ $ { echo hello; }
bash: syntax error near unexpected token `}'
In summary, everyone else is right: use your mouse better.
Yes it is possible for you to ignore the command prompt, when copying commands from web sites. Use the shift and arrow keys to ignore the prompt. This will also help you to ignore the use of the # sign, which is used to indicate commands, which need administrative privileges.

Bash alias using !$

I found out today that I can write !$ to get the last argument from the last command executed.
Now I'm trying to create an alias using that shortcut and it isn't working at all.
These are the ones I'm trying to create.
alias gal='git add !$'
alias gcl='git checkout !$'
alias sl='sublime !$'
And this is the result output when calling gal or gcl
fatal: pathspec '!$' did not match any files
So it seems like !$ just isn't being replaced by the last argument from the last command in this context.
Is it possible?
Instead of fiddling with Bash's history, you might as well want to use Bash's $_ internal variable: The relevant part of the manual states:
$_: […] expands to the last argument to the previous command, after expansion. […]
For example:
$ touch one two three
$ echo "$_"
three
$ ls
$ echo "$_"
ls
$ a='hello world'
$ echo $a
hello world
$ echo "$_"
world
$ echo "$a"
hello world
$ echo "$_"
hello world
$
In your case, your aliases would look like:
alias gal='git add "$_"'
alias gcl='git checkout "$_"'
alias sl='sublime "$_"'
You can use the bash builtin history command fc: an example
$ alias re_echo='echo $(fc -ln -2 | awk '\''NR==1 {print $NF}'\'')'
$ echo foo
foo
$ re_echo bar
foo bar
$ re_echo baz
bar baz
$ re_echo qux
baz qux

Using dot or "source" while calling another script - what is the difference?

Let's take a little example:
$ cat source.sh
#!/bin/bash
echo "I'm file source-1"
. source-2.sh
And:
$ cat source-2.sh
#!/bin/bash
echo "I'm file source-2"
Now run:
$ ./source.sh
I'm file source-1
I'm file source-2
If I'll change the call of the second file in first:
$ cat source.sh
#!/bin/bash
echo "I'm file source-1"
source source-2.sh
It will have the same effect as using dot.
What is difference between these methods?
The only difference is in portability.
. is the POSIX-standard command for executing commands from a file; source is a more-readable synonym provided by Bash and some other shells. Bash itself, however, makes no distinction between the two.
There is no difference.
From the manual:
source
source filename
A synonym for . (see Bourne Shell Builtins).

All arguments into files with correct quoting using "$#"

I need my bashscript to cat all of its parameters into a file. I tried to use cat for this because I need to add a lot of lines:
#!/bin/sh
cat > /tmp/output << EOF
I was called with the following parameters:
"$#"
or
$#
EOF
cat /tmp/output
Which leads to the following output
$./test.sh "dsggdssgd" "dsggdssgd dgdsdsg"
I was called with the following parameters:
"dsggdssgd dsggdssgd dgdsdsg"
or
dsggdssgd dsggdssgd dgdsdsg
I want neither of these two things: I need the exact quoting which was used on the command line. How can I achieve this? I always thought $# does everything right in regards to quoting.
Well, you are right that "$#" has the args including the whitespace in each arg. However, since the shell performs quote removal before executing a command, you can never know how exactly the args were quoted (e.g. whether with single or double quotes, or backslashes or any combination thereof--but you shouldn't need to know, since all you should care for are the argument values).
Placing "$#" in a here-document is pointless because you lose the information about where each arg starts and ends (they're joined with a space inbetween). Here's a way to see just this:
$ cat test.sh
#!/bin/sh
printf 'I was called with the following parameters:\n'
printf '"%s"\n' "$#"
$ ./test.sh "dsggdssgd" "dsggdssgd dgdsdsg"
I was called with the following parameters:
"dsggdssgd"
"dsggdssgd dgdsdsg"
Try:
#!/bin/bash
for x in "$#"; do echo -ne "\"$x\" "; done; echo
To see what's interpreted by Bash, use:
bash -x ./script.sh
or add this to the beginning of your script:
set -x
You might want add this on the parent script.

Bash Expression Evaluation Order on Command Line

Background:
I'm working on quickly calling bash command line expressions inside of SGE's job submission program qSub in parallel. While doing so, I was attempting to submit an expression (as an argument) to be ran inside of another script like so:
./runArguments.sh grep foo bar.txt > output.txt
runArguments.sh looks like this:
#!/bin/bash
${1} ${2} ${3} etc....to 12
The idea is that I want "grep foo bar.txt > output.txt" to be evaluated in the script...NOT ON THE COMMAND LINE. In the example above, "grep foo bar.txt" will evaluate during runArguments.sh execution, but the output redirection would be evaluated on the command line. I eventually found a working solution using "eval" that is shown below, but I do not understand why it works.
Question(s)
1) Why does
./runArguments.sh eval "grep foo bar.txt > output.txt"
allow the eval and the expression to be taken as arguments, but
./runArguments.sh $(grep foo bar.txt > output.txt)
evaluates on the command line before the script is called? (the output of $(grep...) is taken as the arguments instead)
2) Is there a better way of doing this?
Thanks in advance!
Your first question is a bit hard to answer, because you've already answered it yourself. As you've seen, command substitution (the $(...) or `...` notation) substitutes the output of the command, and then processes the result. For example, this:
cat $(echo tmp.sh)
gets converted to this:
cat tmp.sh
So in your case, this:
./runArguments.sh $(grep foo bar.txt > output.txt)
runs grep foo bar.txt > output.txt, grabs its output — which will be nothing, since you've redirected any output to output.txt — and substitutes it, yielding:
./runArguments.sh
(so your script is run with no arguments).
By contrast, this:
./runArguments.sh eval "grep foo bar.txt > output.txt"
does not perform any command substitution, so your script is run with two arguments: eval, and grep foo bar.txt > output.txt. This command inside your script:
${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10} ${11} ${12}
is therefore equivalent to this:
eval grep foo bar.txt '>' output.txt
which invokes the eval built-in with five arguments: grep, foo, bar.txt, >, and output.txt. The eval built-in assembles its arguments into a command, and runs them, and even translates the > and output.txt arguments into an output-redirection, so the above is equivalent to this:
grep foo bar.txt > output.txt
. . . and you already know what that does. :-)
As for your second question — no, there's not really a better way to do this. You need to pass the > in as an argument, and that means that you need to use eval ... or bash -c "..." or the like in order to "translate" it back into meaning output-redirection. If you're O.K. with modifying the script, then you might want to change this line:
${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10} ${11} ${12}
to this:
eval ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10} ${11} ${12}
so that you don't need to handle this in the parameters. Or, actually, you might as well change it to this:
eval ${#}
which will let you pass in more than twelve parameters; or, better yet, this:
eval "${#}"
which will give you slightly more control over word-splitting and fileglobbing and whatnot.

Resources