how to exec multiple commands from a single variable from a tcl file - linux

I am trying to make a custom MOTD header using a tcl file. I've already been succuessful in adding the commands to the last line of the /etc/profile
cowsay -f $(ls /usr/share/cowsay/cows/ | shuf -n1) $(whoami), $(fortune)
I want to add this into my existing MOTD but I do not know the proper syntax to exec multiple commands without the pipe break command. As you can see below I have tried:
#!/usr/bin/env tclsh
# * Variable
set cows [exec -- /usr/bin/whoami | /usr/games/fortune | cowsay]
# * Output
puts "$cows"
which outputs the fortune and cowsay fine but, I cannot seem to get the whoami command to exec up with the other commands.
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Any help regarding how multiple commands are executed from within the syntax of the tcl format would greatly be appreciated, thanks y'all.

The answer by iagreen is of course correct and probably more maintainable, but to answer your subquestion: how to translate $(some command) into tcl:
In bash, the $(...) syntax executes the string captured by the parenthesis by evaluating it in another shell - a new instance of bash often referred to as a subshell.
In tcl, the exec command executes its arguments as a list of words to be evaluated in a subshell.
So, putting two and two together the correct translation of $(...) is [exec ...].
Therefore, the direct translation of this:
cowsay -f $(ls /usr/share/cowsay/cows/ | shuf -n1) $(whoami), $(fortune)
is this:
exec cowsay -f [exec ls /usr/share/cowsay/cows/ | shuf -n1] \
[exec whoami], [exec fortune]
Which is basically the same as the answer given by iagreen.

The problem with your approach is fortune ignores stdin.
It looks like you are building a string to pass to cowsay. Why not build the string in pieces using Tcl strings. For example,
#!/usr/bin/env tclsh
# * Variable
set cowsParam [exec /usr/bin/whoami]
append cowsParam ", " [exec /usr/games/fortune]
set cowImage [exec ls /usr/share/cowsay/cows/ | shuf -n1]
set cows [exec cowsay -f $cowImage $cowsParam]
# * Output
puts "$cows"

Related

Shell Script intend to read argument after '|'

Hi I was wondering how to read the argument after "|" pipe from shell script.
For example, when I run ./tmp.sh ls -la | sort
I could only get 2 arguments, which is "ls" and "-la".
Is there any way to read "| sort" without modifying the command, and realize only with shell script?
Thanks a lot!!
One way would be to pass the entire command as a string to your script.
./tmp.sh -c "ls -la | sort"
...or without a flag...
./tmp.sh "ls -la | sort"
Afterward, you can split the string into an array in your script.
I guess you could check ps axf, but part of the beauty of pipes is the loose coupling they give, because bash knows what is in your pipeline, not the individual pieces of the pipeline. This makes writing filters simple.

How to use output directly as a command

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.

bash escape exclamation character inside variable with backtick

I have this bash script:
databases=`mysql -h$DBHOST -u$DBUSER -p$DBPASSWORD -e "SHOW DATABASES;" | tr -d "| " | grep -v Database`
and the issue is when the password has all the characters possible. how can i escape the $DBPASSWORD in this case? If I have a password with '!' and given the fact that command is inside backticks. I have no experience in bash scripts but I've tried with "$DBPASSWORD" and with '$DBPASSWORD' and it doesn't work. Thank you
LATER EDIT: link to script here, line 170 -> https://github.com/Ardakilic/backmeup/blob/master/backmeup.sh
First: The answer from #bishop is spot on: Don't pass passwords on the command line.
Second: Use double quotes for all shell expansions. All of them. Always.
databases=$(mysql -h"$DBHOST" -u"$DBUSER" -p"$DBPASSWORD" -e "SHOW DATABASES;" | tr -d "| " | grep -v Database)
Don't pass the MySQL password on the command line. One, it can be tricky with passwords containing shell meta-characters (as you've discovered). Two, importantly, someone using ps can sniff the password.
Instead, either put the password into the system my.cnf, your user configuration file (eg .mylogin.cnf) or create an on-demand file to hold the password:
function mysql() {
local tmpfile=$(mktemp)
cat > "$tmpfile" <<EOCNF
[client]
password=$DBPASSWORD
EOCNF
mysql --defaults-extra-file="$tmpfile" -u"$DBUSER" -h"$DBHOST" "$#"
rm "$tmpfile"
}
Then you can run it as:
mysql -e "SHOW DATABASES" | tr -d "| " ....
mysql -e "SELECT * FROM table" | grep -v ...
See the MySQL docs on configuration files for further examples.
I sometimes have the same problem when automating activities:
I have a variable containing a string (usually a password) that is set in a config file or passed on the command-line, and that string includes the '!' character.
I need to pass that variable's value to another program, as a command-line argument.
If I pass the variable unquoted, or in double-quotes ("$password"), the shell tries to interpret the '!', which fails.
If I pass the variable in single quotes ('$password'), the variable isn't expanded.
One solution is to construct the full command in a variable and then use eval, for example:
#!/bin/bash
username=myuser
password='my_pass!'
cmd="/usr/bin/someprog -user '$username' -pass '$password'"
eval "$cmd"
Another solution is to write the command to a temporary file and then source the file:
#!/bin/bash
username=myuser
password='my_pass!'
cmd_tmp=$HOME/.tmp.$$
touch $cmd_tmp
chmod 600 $cmd_tmp
cat > $cmd_tmp <<END
/usr/bin/someprog -user '$username' -pass '$password'
END
source $cmd_tmp
rm -f $cmd_tmp
Using eval is simple, but writing a file allows for multiple complex commands.
P.S. Yes, I know that passing passwords on the command-line isn't secure - there is no need for more virtue-signalling comments on that topic.

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

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