What does run_command() { echo "+" "$#"; "$#"; } means in bash script? - bash

So I am new learning bash script and I came up with the following piece of code.
run_command() {
echo "+" "$#"
"$#"
}
I am confused on what "$#" means and why is it twice?
Thanks a lot for your time and have a great day.

Aagam Jain's got the answer. I will add some explanation that wouldn't fit in a comment section. I apologize for the verbosity.
Consider this example.
Showing parameters given to a script
test.sh:
echo "$1"
echo "$2"
Let's run this script and give it 2 parameters.
$> bash test.sh ls -l
Result:
ls
-l
First parameter ls, represented by $1, is echo'ed in the first line. Second parameter -l, represented by $2, is echo'ed in the second line.
Bash manual - let's see what it says
($#) Expands to the positional parameters, starting from one
See this: https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters
How does that impact our example? Let's change test.sh a bit.
Expanding parameters starting from one
test.sh:
echo "$#"
Let's run it.
$> bash test.sh ls -l
Result:
ls -l
$# listed both parameters in the same line one after the other. If you had 5 parameters, they'd be printed one after the other.
Let's change test.sh a bit more.
Adding a + to the echo
test.sh:
echo "+" "$#"
Let's run it.
$> bash test.sh ls -l
Result:
+ ls -l
That means, a + appeared before both parameters were printed.
Change test.sh a bit more.
Executing all provided parameters
test.sh:
echo "+" "#"
"$#"
Let's run this.
bash test.sh ls -l
Result:
+ ls -l
total 4
-rw-r--r-- 1 eapo users 0 Sep 23 19:38 file1
-rw-r--r-- 1 eapo users 19 Sep 23 19:38 test.sh
Great. As the commenters and Aagam mentioned, the script printed out what it was going to execute (using echo "+" "$#") and then executed the command. The "$#" basically is just doing ls -lh. Terminal just executes it as is.
Let's put a function in the script now.
Adding a function in the script
test.sh:
run_command() {
echo "+" "$#"
"$#"
}
run_command ls -l
Note that we are executing the function in the script itself instead of giving it on the command line
Let's run it.
bash test.sh
Result:
+ ls -l
total 4
-rw-r--r-- 1 eapo users 0 Sep 23 19:38 file1
-rw-r--r-- 1 eapo users 58 Sep 23 19:41 test.sh
Hope the examples walk you through how the script functions.

This prints the command and its output.
e.g.
run_command() {
echo "+" "$#"
"$#"
}
run_command ls
#output
#+ ls
#files_list_in_current_directory

Related

shell script - run list of commands

for i in `cat foo.txt`
do
$i
done
And I have a input file "foo.txt", with list of commands.
ls -ltr | tail
ps -ef | tail
mysql -e STATUS | grep "^Uptime"
when I run the shell script, it executes, but splits the commands in each line at spaces i.e for first line it executes only "ls", then "-ltr" for which I get command not found error.
How can I run each list as one command?
why am I doing this?
I execute lot of arbitrary shell commands including DB commands. I need to have a error handling as I execute each command(each line from foo.txt), I can't think of what can go wrong, so the idea is put all commands in order and call them in loop and check for error (#?) at each line and stop on error.
Why not just do this?
set -e
. ./foo.txt
set -e causes the shell script to abort if a command exits with a non-zero exit code, and . ./foo.txt executes commands from foo.txt in the current shell.
but I guess I can't send notification (email).
Sure you can. Just run the script in a subshell, and then respond to the result code:
#!/bin/sh
(
set -e
. ./foo.txt
)
if [ "$?" -ne 0 ]; then
echo "The world is on fire!" | mail -s 'Doom is upon us' you#youremail.com
fi
Code mentioned.
for i in `cat foo.txt`
do
$i
done
Please use https://www.shellcheck.net/
This will result _
$ shellcheck myscript
Line 1:
for i in `cat foo.txt`
^-- SC2148: Tips depend on target shell and yours is unknown. Add a shebang.
^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.
^-- SC2006: Use $(...) notation instead of legacy backticked `...`.
Did you mean: (apply this, apply all SC2006)
for i in $(cat foo.txt)
$
Will try while loop, and for test purpose content of foo.txt mentioned below
cat foo.txt
ls -l /tmp/test
ABC
pwd
while read -r line; do $line; if [ "$?" -ne 0 ]; then echo "Send email Notification stating $line Command reported error "; fi; done < foo.txt
total 0
-rw-r--r--. 1 root root 0 Dec 24 11:41 test.txt
bash: ABC: command not found...
Send email Notification stating ABC Command reported error
/tmp
In case error reported you can break the loop.
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_05.html
while read -r line; do $line; if [ "$?" -ne 0 ]; then echo "Send email Notification stating $line Command reported error "; break; fi; done < foo.txt
total 0
-rw-r--r--. 1 root root 0 Dec 24 11:41 test.txt
bash: ABC: command not found...
Send email Notification stating ABC Command reported error
while read -r line; do eval $line; if [ "$?" -ne 0 ]; then echo "Send email Notification stating $line Command reported error "; break; fi; done < foo.txt
total 0
-rw-r--r--. 1 root root 0 Dec 24 11:41 test.txt
bash: ABC: command not found...
Send email Notification stating ABC Command reported error

Which process executes script in terminal

when i write ./test1.sh in the MacOS terminal which process executes the script ?
I have installed on my machine oh-my-zsh and thus run zsh in my terminal.
Running test1.sh like so:
./test1.sh
outputs
1 март 2016/ 1 януари 2015/
1 март 2016/
./test3.sh: line 14: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
./test3.sh: line 15: януари: syntax error: invalid arithmetic operator (error token is "?нуари")
blah
while running it like this
zsh test3.sh
outputs
1 март 2016/
1 януари 2015/
blah
On the other hand running
declare -A newarray
newarray[януари]="1"
qn="януари"
echo ${newarray[$qn]}
outputs
1
Why is that ?
test3.sh
# backup IFS
SAVEIFS=$IFS
# set IFS to newline
IFS=$(echo -en "\n\b")
# get files
FILES=$(ls -1 -d */)
echo ${FILES}
IFS='\n'
read dirsNameArray <<< ${FILES}
echo ${dirsNameArray[0]}
declare -A monthMap
monthMap['януари']="1"
# monthMap[февруари]="2"
# monthMap[март]="3"
# monthMap[април]="4"
# monthMap[май]="5"
# monthMap[юни]="6"
# monthMap[юли]="7"
# monthMap[август]="8"
# monthMap[септември]="9"
# monthMap[октомври]="10"
# monthMap[ноември]="11"
# monthMap[декември]="12"
# iterate over files
IFS='\n'
for f in $FILES
do
echo "blah"
IFS=' '
# read -r dirNameArray <<< $f
# echo "${monthMap[${dirNameArray[2]}]}"
IFS='\n'
done
# restore $IFS
IFS=$SAVEIFS
You should always include a shebang in your scripts. If you want your shell script to be run by zsh then make sure that the topmost line of you script looks like that:
#!/bin/zsh
This will guarantee that your script will be executed by /bin/zsh (or whatever other executable you specified in the shebang).
If you want to find out what shell is used to execute your script, add the following line to it:
ps ho cmd $$
and see what it prints. If you want to know what shell is used in an interactive session, check if either $BASH_VERSION or $ZSH_VERSION is defined.
Let's find out what shell zsh uses to execute text files:
% echo 'ps -f $$' > script.sh && chmod +x script.sh && ./script.sh
UID PID PPID C STIME TTY STAT TIME CMD
slim 17311 16570 0 15:45 pts/0 S+ 0:00 sh ./script.sh
So it uses sh. That make sense, sh is the lowest common denominator, default shell.
To force a different shell, use #! in the first line of the text file:
% echo '#!/bin/zsh' > script.sh && echo 'ps -f $$' >> script.sh && chmod +x script.sh && ./script.sh
UID PID PPID C STIME TTY STAT TIME CMD
slim 17342 16570 0 15:46 pts/0 S+ 0:00 /bin/zsh ./script.sh
#! is a general purpose mechanism, so you can use it to execute pretty much anything that reads from stdin and ignores "comments" beginning with # -- perl, python, most shells, awk, even things like gnuplot.

Why it runs different for source and sh calling in shell

My shell a.sh script like this:
#!/bin/sh
# $ret maybe from database or pipe,whatever it likes:
ret="cnt
1"
echo -e $ret
and calling in different ways produces different results:
$ sh a.sh
cnt 1
$ source a.sh
cnt
1
$
How can I get the same output under sh and source?
How can I get the same output under sh and source?
you need to quote echo. – fedorqui
thanks #fedorqui. that means echo -e "$ret" – tonylee0329
Exactly, quoting echo's argument is the way to adjust the difference between the two shells' echos.

Bash script: how to get the whole command line which ran the script

I would like to run a bash script and be able to see the command line used to launch it:
sh myscript.sh arg1 arg2 1> output 2> error
in order to know if the user used the "std redirection" '1>' and '2>', and therefore adapt the output of my script.
Is it possible with built-in variables ??
Thanks.
On Linux and some unix-like systems, /proc/self/fd/1 and /proc/self/fd/2 are symlinks to where your std redirections are pointing to. Using readlink, we can query if they were redirected or not by comparing them to the parent process' file descriptor.
We will however not use self but $$ because $(readlink /proc/"$$"/fd/1) spawns a new shell so self would no longer refer to the current bash script but to a subshell.
$ cat test.sh
#!/usr/bin/env bash
#errRedirected=false
#outRedirected=false
parentStderr=$(readlink /proc/"$PPID"/fd/2)
currentStderr=$(readlink /proc/"$$"/fd/2)
parentStdout=$(readlink /proc/"$PPID"/fd/1)
currentStdout=$(readlink /proc/"$$"/fd/1)
[[ "$parentStderr" == "$currentStderr" ]] || errRedirected=true
[[ "$parentStdout" == "$currentStdout" ]] || outRedirected=true
echo "$0 ${outRedirected:+>$currentStdout }${errRedirected:+2>$currentStderr }$#"
$ ./test.sh
./test.sh
$ ./test.sh 2>/dev/null
./test.sh 2>/dev/null
$ ./test.sh arg1 2>/dev/null # You will lose the argument order!
./test.sh 2>/dev/null arg1
$ ./test.sh arg1 2>/dev/null >file ; cat file
./test.sh >/home/camusensei/file 2>/dev/null arg1
$
Do not forget that the user can also redirect to a 3rd file descriptor which is open on something else...!
Not really possible. You can check whether stdout and stderr are pointing to a terminal: [ -t 1 -a -t 2 ]. But if they do, it doesn't necessarily mean they weren't redirected (think >/dev/tty5). And if they don't, you can't distinguish between stdout and stderr being closed and them being redirected. And even if you know for sure they are redirected, you can't tell from the script itself where they point after redirection.

How execute bash script line by line?

If I enter bash -x option, it will show all the line. But the script will execute normaly.
How can I execute line by line? Than I can see if it do the correct thing, or I abort and fix the bug. The same effect is put a read in every line.
You don't need to put a read in everyline, just add a trap like the following into your bash script, it has the effect you want, eg.
#!/usr/bin/env bash
set -x
trap read debug
< YOUR CODE HERE >
Works, just tested it with bash v4.2.8 and v3.2.25.
IMPROVED VERSION
If your script is reading content from files, the above listed will not work. A workaround could look like the following example.
#!/usr/bin/env bash
echo "Press CTRL+C to proceed."
trap "pkill -f 'sleep 1h'" INT
trap "set +x ; sleep 1h ; set -x" DEBUG
< YOUR CODE HERE >
To stop the script you would have to kill it from another shell in this case.
ALTERNATIVE1
If you simply want to wait a few seconds before proceeding to the next command in your script the following example could work for you.
#!/usr/bin/env bash
trap "set +x; sleep 5; set -x" DEBUG
< YOUR CODE HERE >
I'm adding set +x and set -x within the trap command to make the output more readable.
The BASH Debugger Project is "a source-code debugger for bash that follows the gdb command syntax."
If your bash script is really a bunch of one off commands that you want to run one by one, you could do something like this, which runs each command one by one when you increment a variable LN, corresponding to the line number you want to run. This allows you to just run the last command again super easy, and then you just increment the variable to go to the next command.
Assuming your commands are in a file "it.sh", run the following, one by one.
$ cat it.sh
echo "hi there"
date
ls -la /etc/passwd
$ $(LN=1 && cat it.sh | head -n$LN | tail -n1)
"hi there"
$ $(LN=2 && cat it.sh | head -n$LN | tail -n1)
Wed Feb 28 10:58:52 AST 2018
$ $(LN=3 && cat it.sh | head -n$LN | tail -n1)
-rw-r--r-- 1 root wheel 6774 Oct 2 21:29 /etc/passwd
Have a look at bash-stepping-xtrace.
It allows stepping xtrace.
xargs: can filter lines
cat .bashrc | xargs -0 -l -d \\n bash
-0 Treat as raw input (no escaping)
-l Separate each line (Not by default for performances)
-d \\n The line separator

Resources