How to use output directly as a command - linux

with this grep it shows a comand I used:
echo `history | grep "ssh root" | head -1| cut -c6-`
with this output:
ssh root#107.170.70.100
I want the output to directly execute as the command instead of printed.
How can I do it?

In principle, this can be done by using the $() format, so
$(history | grep "ssh root" | head -1| cut -c6-)
should do what you ask for. However, I don't think that it is advisable to do so, as this will automatically execute the command that results from your grep, so if you did a mistake, a lot of bad things can happen. Instead I suggest reviewing your result before re-executing. bash history has a lot of nice shortcuts to deal with these kind of things. As an example, imagine:
> history | grep "ssh root"
756 ssh root#107.170.70.100
you can call this command on line 756 easily by typing
!756
It's definitely much safer. Hope this helps.

Ideally you'd be using the $(cmd) syntax rather than the `cmd` syntax. This makes it easier to nest subshells as well as keep track of what's going on.
That aside, if you remove the echo statement it will run the script:
# Prints out ls
echo $( echo ls )
# Runs the ls command
$( echo ls )

Use eval.
$ eval `history | grep "ssh root" | head -1| cut -c6-`
From eval command in Bash and its typical uses:
eval takes a string as its argument, and evaluates it as if you'd typed that string on a command line.
And the Bash Manual (https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html#Bourne-Shell-Builtins)
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.

Related

How to get list of commands used in a shell script?

I have a shell script of more than 1000 lines, i would like to check if all the commands used in the script are installed in my Linux operating system.
Is there any tool to get the list of Linux commands used in the shell script?
Or how can i write a small script which can do this for me?
The script runs successfully on the Ubuntu machine, it is invoked as a part of C++ application. we need to run the same on a device where a Linux with limited capability runs. I have identified manually, few commands which the script runs and not present on Device OS. before we try installing these commands i would like to check all other commands and install all at once.
Thanks in advance
I already tried this in the past and got to the conclusion that is very difficult to provide a solution which would work for all scripts. The reason is that each script with complex commands has a different approach in using the shells features.
In case of a simple linear script, it might be as easy as using debug mode.
For example: bash -x script.sh 2>&1 | grep ^+ | awk '{print $2}' | sort -u
In case the script has some decisions, then you might use the same approach an consider that for the "else" cases the commands would still be the same just with different arguments or would be something trivial (echo + exit).
In case of a complex script, I attempted to write a script that would just look for commands in the same place I would do it myself. The challenge is to create expressions that would help identify all used possibilities, I would say this is doable for about 80-90% of the script and the output should only be used as reference since it will contain invalid data (~20%).
Here is an example script that would parse itself using a very simple approach (separate commands on different lines, 1st word will be the command):
# 1. Eliminate all quoted text
# 2. Eliminate all comments
# 3. Replace all delimiters between commands with new lines ( ; | && || )
# 4. extract the command from 1st column and print it once
cat $0 \
| sed -e 's/\"/./g' -e "s/'[^']*'//g" -e 's/"[^"]*"//g' \
| sed -e "s/^[[:space:]]*#.*$//" -e "s/\([^\\]\)#[^\"']*$/\1/" \
| sed -e "s/&&/;/g" -e "s/||/;/g" | tr ";|" "\n\n" \
| awk '{print $1}' | sort -u
the output is:
.
/
/g.
awk
cat
sed
sort
tr
There are many more cases to consider (command substitutions, aliases etc.), 1, 2 and 3 are just beginning, but they would still cover 80% of most complex scripts.
The regular expressions used would need to be adjusted or extended to increase precision and special cases.
In conclusion if you really need something like this, then you can write a script as above, but don't trust the output until you verify it yourself.
Add export PATH='' to the second line of your script.
Execute your_script.sh 2>&1 > /dev/null | grep 'No such file or directory' | awk '{print $4;}' | grep -v '/' | sort | uniq | sed 's/.$//'.
If you have a fedora/redhat based system, bash has been patched with the --rpm-requires flag
--rpm-requires: Produce the list of files that are required for the shell script to run. This implies -n and is subject to the same limitations as compile time error checking checking; Command substitutions, Conditional expressions and eval builtin are not parsed so some dependencies may be missed.
So when you run the following:
$ bash --rpm-requires script.sh
executable(command1)
function(function1)
function(function2)
executable(command2)
function(function3)
There are some limitations here:
command and process substitutions and conditional expressions are not picked up. So the following are ignored:
$(command)
<(command)
>(command)
command1 && command2 || command3
commands as strings are not picked up. So the following line will be ignored
"/path/to/my/command"
commands that contain shell variables are not listed. This generally makes sense since
some might be the result of some script logic, but even the following is ignored
$HOME/bin/command
This point can however be bypassed by using envsubst and running it as
$ bash --rpm-requires <(<script envsubst)
However, if you use shellcheck, you most likely quoted this and it will still be ignored due to point 2
So if you want to use check if your scripts are all there, you can do something like:
while IFS='' read -r app; do
[ "${app%%(*}" == "executable" ] || continue
app="${app#*(}"; app="${app%)}";
if [ "$(type -t "${app}")" != "builtin" ] && \
! [ -x "$(command -v "${app}")" ]
then
echo "${app}: missing application"
fi
done < <(bash --rpm-requires <(<"$0" envsubst) )
If your script contains files that are sourced that might contain various functions and other important definitions, you might want to do something like
bash --rpm-requires <(cat source1 source2 ... <(<script.sh envsubst))
Based #czvtools’ answer, I added some extra checks to filter out bad values:
#!/usr/bin/fish
if test "$argv[1]" = ""
echo "Give path to command to be tested"
exit 1
end
set commands (cat $argv \
| sed -e 's/\"/./g' -e "s/'[^']*'//g" -e 's/"[^"]*"//g' \
| sed -e "s/^[[:space:]]*#.*\$//" -e "s/\([^\\]\)#[^\"']*\$/\1/" \
| sed -e "s/&&/;/g" -e "s/||/;/g" | tr ";|" "\n\n" \
| awk '{print $1}' | sort -u)
for command in $commands
if command -q -- $command
set -a resolved (realpath (which $command))
end
end
set resolved (string join0 $resolved | sort -z -u | string split0)
for command in $resolved
echo $command
end

Echo output of a piped command

I am trying to just echo a command within my bash script code.
OVERRUN_ERRORS="$ifconfig | egrep -i "RX errors" | awk '{print $7}'"
echo ${OVERRUN_ERRORS}
however it gives me an error and the $7 does not show up in the command. I have to store it in a variable, because I will process the output (OVERRUN_ERRORS) at a later point in time. What's the right syntax for doing this? Thanks.
On Bash Syntax
foo="bar | baz"
...is assigning the string "bar | baz" to the variable named foo; it doesn't run bar | baz as a pipeline. To do that, you want to use command substitution, in either its modern $() syntax or antiquated backtick-based form:
foo="$(bar | baz)"
On Storing Code For Later Execution
Since your intent isn't clear in the question --
The correct way to store code is with a function, whereas the correct way to store output is in a string:
# store code in a function; this also works with pipelines
get_rx_errors() { cat /sys/class/net/"$1"/statistics/rx_errors; }
# store result of calling that function in a string
eth0_errors="$(get_rx_errors eth0)"
sleep 1 # wait a second for demonstration purposes, then...
# compare: echoing the stored value, vs calculating a new value
echo "One second ago, the number of rx errors was ${eth0_errors}"
etho "Right now, it is $(get_rx_errors eth0)"
See BashFAQ #50 for an extended discussion of the pitfalls of storing code in a string, and alternatives to same. Also relevant is BashFAQ #48, which describes in detail the security risks associated with a eval, which is often suggested as a workaround.
On Collecting Interface Error Counts
Don't use ifconfig, or grep, or awk for this at all -- just ask your kernel for the number you want:
#!/bin/bash
for device in /sys/class/net/*; do
[[ -e $device/statistics/rx_errors ]] || continue
rx_errors=$(<"${device}/statistics/rx_errors")
echo "Number of rx_errors for ${device##*/} is $rx_errors"
done
Use $(...) to capture the output of a command, not double quotes.
overrun_errors=$(ifconfig | egrep -i "RX errors" | awk '{print $7}')
Your double quotes around RX errors are a problem. Try;
OVERRUN_ERRORS="$ifconfig | egrep -i 'RX errors' | awk '{print $7}'"
To see the commands as they are executing, you can use
set -v
or
set -x
For example;
set -x
OVERRUN_ERRORS="$ifconfig | egrep -i 'RX errors' | awk '{print $7}'"
set +x

How to make bash to know | is a pipe and not a string

Hi my question is simple. I want to do this in a command prompt.
var="ls | cat"
$var
Now I know that when I try to do this manually
ls | cat
Bash takes | as a special thing. I don't know how its called, I know | it's called a pipe but I mean that bash takes | as a ... and actually makes a pipe. I also figured that when I try to do $var bash actually takes | as a string and not as a pipe. Well, my question is How can I make bash to realize that | is actually a pipe and not a string. Thanks, I hope I am clear about my point.
Simple solution: use eval:
var="ls | cat"
eval $var
bash interprets the arguments to eval as if you had typed that on the command line.
Of course, keep in mind the security risks to using eval with user input, in case that's an issue for your program.
This may or may not apply - but it sounds like you may be looking for the alias command. You can do alias var="ls | cat" and then in your command prompt you can do var and it treats it as if you wrote ls | cat
Rather than trying to embed executable code into a variable (which should be used to hold data, not code), use a shell function, which is intended to hold code:
my_func () {
ls | cat
}
| is called a pipe, I haven't heard any other naming. Basically the stream output by the command on its left goes as the input to the command on its right. In your case, ls output goes into a stream (i.e. a temporary file), and that stream is fed to cat. cat prints the content of a file, and ls stream is very much like a file.
Now, you are trying to make bash interpret your variable var. To do this, try:
var=`ls | cat`
$var
On my computer I get this:
-bash: Applications: command not found
Because in my case, $var is expanded to Applications Documents Downloads, the output of my ls.
Given crudely as is, bash believes this is a command I want him to execute.
If your intention is not to execute $varcontent but print it, try:
var=`ls | cat`
echo $var
The cat is not needed here, just use ls -1 and as other answers say you can alias it or put it in a function.
For example, if you want to override ls to print each file on a new line do something like
> alias ls='command ls -1'
> ls
file1
file2
etc...
And put it in a bash init file like ~/.bashrc if you want to make the change permanent
1) Functions are suitable for such tasks:
func (){
ls | cat
}
Invoke it by saying func
2) Also another suitable solution could be eval:
eval takes a string as its argument, and evaluates it as if you'd typed that string on a command line. (If you pass several arguments, they are first joined with spaces between them.)
var="ls | cat"
eval $var

Bash, Escaping quotes in command construction,

I have an array of numbers - sipPeers.
I want to iterate trough that array and pass each value into the command
asterisk -rx "sip show peer 1234" - where 1234 is obviously an extension.
The output of sip show peer is piped and manipulated twice to output one value which I want to store in a second array sipUserAgent.
temp="asterisk -rx \"sip show peer "${sipPeers[q]}"\" | grep Useragent | awk \'{print$3}\'" #(1)
echo $temp #(2)
sipUserAgent[q]=$($temp) #(3)
The output of one iteration yields the constructed command as a string (2) and the tries to execute it (3):
asterisk -rx "sip show peer 564" | grep Useragent | awk '{print }'
No such command '"sip' (type 'core show help sip' for other possible commands)
If I copy and paste the echo'd command it runs, but when the script executes I get that error. Somewhere the " character changes meaning when the scipt executes it?
Don't try to store the command in a variable before executing it. There are all sorts of problems with doing this, and you're running into several of them (see BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!). Just put the pipeline directly inside $( ... ) where it belongs:
sipUserAgent[q]=$(asterisk -rx "sip show peer ${sipPeers[q]}" | grep Useragent | awk '{print $3}')
1st problem
In the step (1) you write: awk \'{print$3}\'
In the output of (2) you receive: awk '{print }'
The problem is an missed escape of $3
Try is again with awk \'{print\$3}\' in (1)
2nd problem
sipUserAgent[q]=$($temp) tries to invoke a command asterisk_-rx...
This is surely not what you want.
The solution is to use eval - see help eval for more informations.
example
cmd="echo TEST"
$(${cmd})
gives:
TEST: command not found
cmd="echo TEST"
eval ${cmd}
gives:
TEST
Note about code style
If possible it is better to avoid complex command creation and execution via eval.
Better is to write more simple code, if applicable.
So you should use (as also mentioned by Gordon Davisson in another answer):
sipUserAgent[q]=$(asterisk -rx "sip show peer ${sipPeers[q]}" \
| grep Useragent \
| awk '{print $3}' \
)
I am not aware of a way to escape quotes during command substitution, but a workaround is to use eval. See the following example:
$ cat test.sh
echo "$1"
$ temp="./test.sh \"2 34 434\" "
$ echo "$temp"
./test.sh "2 34 434"
$ result=$(eval $temp)
$ echo "$result"
2 34 434
$ result=$($temp)
$ echo "$result"
"2
Note that using eval is generally considered bad practice in most situations, if you can avoid it.

Setting an environment variable in csh

I have the following line at the first line in my script file:
#!/bin/sh
So I'm using csh.(?)
I wanto assign the output of the following to an environment variable:
echo $MYUSR | awk '{print substr($0,4)}'
I try:
set $MYVAR = echo $MYUSR | awk '{print substr($0,4)}'
But it doesn't work,
How can I do it? I want to do it in a sh file.
Your script should look like
#!/bin/csh
set MYVAR = `echo $MYUSR | awk '{print substr($0,4)}'`
echo $MYVAR
I don't have a way to test this right now, let me now if it doesn't work.
If you've inherited the basis of your script from someone else, with the #!/bin/sh,
then you have to find out if /bin/sh is really the bourne shell, or if it is a link to /bin/bash
You can tell that by doing
ls -l /bin/sh /bin/bash
if you get back information on files where the size is exactly the same, the you're really using bash, but called as /bin/sh
So try these 2 solutions
MYVAR=$(echo $MYUSR | awk '{print substr($0,4)}')
echo $MYVAR
AND
MYVAR=``echo $MYUSR | awk '{print substr($0,4)}``
echo $MYVAR
# arg!! only one pair of enclosing back-ticks needed,
# can't find the secret escape codes to make this look exactly right.
in all cases (csh) included, the back-ticks AND the $( ... ) are known as command substitution.
What every output comes from running the command inside, is substituted into the command line AND then the whole command is executed.
I hope this helps.
if it's /bin/sh it's bourne shell or bash, and use back quotes to execute something and this to assign that...
MYVAR=`echo $MYUSR | awk ...`
That script first line indicates that it should be interpreted by the Bourne shell (sh), not csh. Change it to
#!/bin/csh
The first line of your code shows clearly you are not using a csh. You are using a plain sh environment/shell. You have 2 options:
Either change the first line to #!/bin/csh OR
Keeping first line unchanged, update the code for setting the variable.
MYVAR=`echo $MYUSR | awk '{print substr($0,4)}`
echo $MYVAR
Let me know, if you get any error.

Resources