From /usr/local/bin/erl
ROOTDIR=/usr/local/lib/erlang
BINDIR=$ROOTDIR/erts-5.9.1/bin
EMU=beam
PROGNAME=`echo $0 | sed 's/.*\///'`
export EMU
export ROOTDIR
export BINDIR
export PROGNAME
exec $BINDIR/erlexec ${1+"$#"}
I know "$#" meams arguments. But {1+"$#"} means what?
From IEEE Std 1003.1 (POSIX 2013), Shell command language:
${parameter:+[word]}
Use Alternative Value. If parameter is unset or null, null shall be substituted; otherwise, the expansion of word (or an empty string if word is omitted) shall be substituted.
I.e., ${1+"$#"} expands to the value of "$#", the command line arguments, except when $1 is not set, i.e. there are no command line arguments, in which case the expression expands to nothing. A simpler script that shows how this works is
echo '"' ${1+"$#"} '"'
If you store this in a file test.sh and run it, you get:
/tmp$ sh test.sh
" "
/tmp$ sh test.sh 1
" 1 "
/tmp$ sh test.sh 1 2
" 1 2 "
(The spaces at the begin and end come from echo.)
larsmans provides an explanation of the semantics, but doesn't clarify why ${1+"$#"} is different than simply "${#}". In a properly behaving shell, "${#}" expands to nothing. That is: foo "$#" should call foo with no arguments if "$#" is empty. In an incorrectly behaving shell, foo "$#" will be invoked with one argument (the empty string). Many historical shells expanded "$#" to the empty string rather than to nothing, but ${1+"$#"} correctly expands to nothing.
Related
I have a script that takes in several arguments.
I need everything but $1 and $2 in a string.
I have tried this:
message="$*"
words= $(grep -v "$2"|"$3" $message)
but it doesn't work, it gives me the error:
./backup: line 26: First: command not found
Use shift 2 to shift the arguments along (it drops the first n arguments).
If you need "$1" and "$2" for later, save them in variables first.
Note that in shell, assignments to variables cannot have whitespace either side of the =.
First=$1
Second=$2
shift 2
Message=$#
Maybe something like this?
[root#tsekmanrhel771 ~]# cat ./skip1st2.sh
#!/bin/bash
COUNT=0
for ARG in "$#"
do
COUNT=$[COUNT + 1]
if [ ${COUNT} -gt 2 ]; then
RESULT="${RESULT} ${ARG}"
fi
done
echo ${RESULT}
[root#tsekmanrhel771 ~]# ./skip1st2.sh first second third 4 5 6 7
third 4 5 6 7
You can use a subarray:
$ set -- arg1 arg2 arg3 arg4
$ str=${*:3}
$ echo "$str"
arg3 arg4
More often than not, it's good practice to preserve the arguments as separate elements, though, which you can do by using $# and assigning to a new array:
$ arr=("${#:3}")
$ declare -p arr
declare -a arr=([0]="arg3" [1]="arg4")
Notice that in str=${*:3}, quoting isn't necessary, but in arr=("${#:3}"), it is (or the arguments would be split on whitespace).
As for your error message: your command
words= $(grep -v "$2"|"$3" $message)
does the following:
It sets a variable words to the empty string for the environment of the command (because there is a blank after =).
It tries to set up a pipeline consisting of two commands, grep -v "$2" and "$3" $message. The first of these commands would just hang and wait for input; the second one tries to run the contents of $3 as a command; presumably, based on your error message, $3 contains First.
If the pipeline would actually run, its output would be run as a command (again because of the blank to the right of =).
I have a bash script which executes about 20 commands and for debugging purposes I find myself scrolling through the output a lot.
Unfortunately bash doesn't tell me which part of the output is part of what command.
When I use "set -x" in the script it at least prints some information on what it just executed, but I don't really like the output it generates.
For instance, if I have this script:
#!/bin/bash
set -x
echo "foo"
if [ "$ASD" == "QWE" ] ; then
echo "bar"
fi
I would like the output to be something like this:
echo "foo"
foo
echo "bar"
bar
Or maybe:
echo "foo"
foo
if [ "value_of_ASD" == "QWE" ] ; then
echo "bar"
bar
fi
Instead of printing the commands in bold, highlighting with a color would also be okay. But I don't just want to have "+" characters in front of the commands and I also don't like the if statements showing up like '[' value_of_ASD == QWE ']'.
How can I accomplish that with bash?
At the moment the output looks like this btw:
+ echo foo
foo
+ '[' value_of_ASD == QWE ']'
+ echo bar
bar
Edit:
One idea I had was to write a script that I would source in the very beginning of the main script and then let the sourced script parse the main one. Something like this:
source_me.sh
#!/bin/bash
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/$(basename $0)"
FORMAT_SET_BOLD='\033[0;1m'
FORMAT_RESET='\033[0m'
cat $SCRIPT_PATH | tail -n "+$START_LINE" | while read line; do
printf "${FORMAT_SET_BOLD}${line}${FORMAT_RESET}\n"
eval "${line}"
done
exit 0;
main.sh
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
echo "Bar"
echo "+Hello"
The output in that case is:
echo "Foo"
Foo
echo "Bar"
Bar
echo "+Hello"
+Hello
But this method will fail if I use more complex code that goes over multiple lines (if statements, loops etc):
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
if [ "$FOOBAR" == "" ] ; then
echo "Bar"
fi
echo "+Hello"
In this case I get:
echo "Foo"
Foo
if [ "$FOOBAR" == "" ] ; then
./source_me.sh: eval: line 9: syntax error: unexpected end of file
echo "Bar"
Bar
fi
./source_me.sh: eval: line 8: syntax error near unexpected token ´fi'
./source_me.sh: eval: line 8: ´fi'
echo "+Hello"
+Hello
I would like to extend rubo77's answer with a few examples that I think deserve a separate answer:
Plain text prefix
So the basic example is to set PS4 to some plain text, e.g.:
PS4="# "; set -x
Which will result in:
Color & extra line text prefix
But because you can use special characters and ANSI escape codes you can for example add a new line before each new command and print the prefix in a color, e.g.:
PS4="\n\033[1;33m>>>\033[0m "; set -x
Result:
Dynamic color prefix
Finally you can make the command prefix call other programs with each use, which you can use to add a timestamp, e.g.:
# yes, there have to be single quotes below, not double!
PS4='\033[1;34m$(date +%H:%M:%S)\033[0m '; set -x
Result:
You can change the + to a string of your desire by setting PS4, e.g.:
PS4="# "; set -x
Note that set -x has no innate knowledge of your terminal, which would be required to send the correct characters to create bold or coloured text.
If you can capture all output through a single file handle, you can probably do this with a pipe:
$ /path/to/your/script 2>&1 | sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/"
You might want to man tput for more information about the tool we're using to get bold type.
The viability of this depends on many factors I don't know about your environment. YMMV, may contain nuts.
Note:
You might think it was better to capture stderr separately with a line like this:
$ /path/to/your/script 2> >(sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/")
Alas, this doesn't behave consistently, because stderr passing through the pipe may not be submitted to the terminal before subsequent stdout from the script itself. Note also that this solution, because it uses process substitution, is a bashism and is not portable to POSIX.
What you are looking for is set -v.
-v prints out the line the interpreter just read.
-x prints out the post-parsing results of the line that the interpreter just read.
file x:
set -vx
foo=1
echo $foo
set +vx
Executing:
$: . x
foo=1
++ foo=1
echo $foo
++ echo 1
1
set +vx
++ set +vx
Below is the code of bash:
a=`echo hello`
echo $a
output is :
hello
But I think it should be:
hello
0
You think wrong ;-)
Putting the command in backticks assigns the output (stdout) from the expression on the right to the variable on the left.
$? gives you the "output status" (or return code) of the command - aka the "0" you were expecting.
So:
a=`echo hello`
Runs the command "echo hello" but instead of echoing to stdout, it "echoes" to varaiable a. So a=whatever_the_command_would_have_written_to_stdout (in this case "hello") - nothing is actually written to stdout because it is "captured" by the ``s
You mistakenly think that a=`echo hello`:
executes echo hello and prints its stdout output directly to the caller's stdout,
and then assigns the exit code (return value) of the echo command to variable $a.
Neither is true; instead:
echo hello's stdout output is captured in memory (without printing to the caller's stdout; that's how command substitutions work),
and that captured output is assigned to $a.
A command's exit code (a return value indicating success vs. failure) is never directly returned in POSIX-like shells such as Bash.
The only way to use an exit code is either:
explicitly, by accessing special variable $? immediately after the command ($? contains the most recent command's exit code)
implicitly, in conditionals (a command whose exit code is 0 evaluates to true in a conditional, any other exit code implies false).
Thus, to achieve what you're really trying to do, use:
echo 'hello' # Execute a command directly (its stdout output goes to the caller's stdout)
a=$? # Save the previous command's exit code in var. $a
echo "$a" # Echo the saved exit code.
As this [ this ] answer already mentioned, the return value for the last executed command is stored in
$? # ${?} is sometimes needed
If you wish a to contain 'hello' and the return value of echo hello in separate lines, ie
hello
0
below is one way to do it
$ a=`echo -en "hello\n" && echo -n $?` # here $? is ret val for 1st echo
$ echo -e "$a"
hello
0
Note
-n with echo suppresses the trailing new line
-e with echo interprets escape sequences
Since && is the logical and operator, the second echo wouldn't have been executed had the first echo failed
Another important point to note is that even the assignment ie
a=b
has a return value.
I found some strange thing in bash and I can't understand how it works.
[test ~]$ a=""
[test ~]$ $a && echo 1
1
[test ~]$ $a
[test ~]$ echo $?
0
Why does $a (which is empty) return 0? Is it somehow transformed to empty command?
If I add quotes or write empty string before &&, it will return error. While empty command returns 0.
[test ~]$ "$a" && echo 1
-bash: : command not found
[test ~]$ "" && echo 1
-bash: : command not found
[test ~]$ `` && echo 1
1
So, what is happening when I type $a?
You seem to confuse bash with some other programming language. Variables get replaced, then what is left gets executed.
"$a"
This is the content of a, between quotation marks. a is empty, so this is equivalent to:
""
That is not a command. "Command not found." As there was an error, the execution was not successful (shell return code is not 0), so the second half of the command -- && echo 1 -- does not get executed.
Backticks...
``
...execute whatever is between them, with the output of that command replacing the whole construct. (There is also $() which does the same, and is less prone to being overlooked in a script.) So...
`echo "foo"`
...would evaluate to...
foo
...which would then be executed. So your...
``
...evaluates to...
<empty>
...which is then "executed successfully" (since there is no error).
If you want to test the contents of a, and execute echo 1 only if a is not empty, you should use the test command:
test -n "$a" && echo 1
There is a convenient alias for test, which is [, which also conveniently ignores a trailing ]...
[ -n "$a" ] && echo 1
...and a bash-ism [[ that "knows" about variable replacement and thus does not need quotation marks to avoid complaining about a missing argument if $a does indeed evaluate to empty...
[[ -n $a ]] && echo 1
...or, of course, the more verbose...
if [[ -n $a ]]
then
echo 1
fi
Ah. Missed the core part of the question:
$a && echo 1
This is two statements, separated by &&. The second statement only gets executed if the first one executes OK. The bash takes the line apart and executes the first statement:
$a
This is...
<empty>
...which is "successful", so the second statement gets executed. Opposed to that...
&& echo 1
...is a syntax error because there is no first statement. ;-) (Tricky, I know, but that's the way this cookie crumbles.)
a=""
or
a=" " #a long empty string
then
$> $a
will return 0
$> $noExistVar
will also return 0.
They get "executed", in fact, nothing gets executed. same as you press enter or pressing 10 spaces then enter, you get return code 0 too.
$> && echo 1
this will fail, because bash will try to execute the first part, in this case it is missing.
$> $notExistVar && echo 1
Here it works, I guess bash found the first part the $whatever, therefore no syntax error. Then "execute" it, well nothing to execute, return 0, (same as pressing enter after prompt), then check, if first part returned 0, exec the cmd after &&.
I said guess because I didn't check bash's source codes. Please correct me if it is wrong.
the $> " " && echo 1 case, I think it is clear, don't need to explain.
Who can simply explain
what is the difference between $* and $#?
Why there are two variables for same content as above?
There is no difference if you do not put $* or $# in quotes. But if you put them inside quotes (which you should, as a general good practice), then $# will pass your parameters as separate parameters, whereas $* will just pass all params as a single parameter.
Take these scripts (foo.sh and bar.sh) for testing:
>> cat bar.sh
echo "Arg 1: $1"
echo "Arg 2: $2"
echo "Arg 3: $3"
echo
>> cat foo.sh
echo '$* without quotes:'
./bar.sh $*
echo '$# without quotes:'
./bar.sh $#
echo '$* with quotes:'
./bar.sh "$*"
echo '$# with quotes:'
./bar.sh "$#"
Now this example should make everything clear:
>> ./foo.sh arg1 "arg21 arg22" arg3
$* without quotes:
Arg 1: arg1
Arg 2: arg21
Arg 3: arg22
$# without quotes:
Arg 1: arg1
Arg 2: arg21
Arg 3: arg22
$* with quotes:
Arg 1: arg1 arg21 arg22 arg3
Arg 2:
Arg 3:
$# with quotes:
Arg 1: arg1
Arg 2: arg21 arg22
Arg 3: arg3
Clearly, "$#" gives the behaviour that we generally want.
More detailed description:
Case 1: No quotes around $* and $#:
Both have same behaviour.
./bar.sh $* => bar.sh gets arg1, arg2 and arg3 as separate arguments
./bar.sh $# => bar.sh gets arg1, arg2 and arg3 as separate arguments
Case 2: You use quotes around $* and $#:
./bar.sh "$*" => bar.sh gets arg1 arg2 arg3 as a single argument
./bar.sh "$#" => bar.sh gets arg1, arg2 and arg3 as a separate arguments
More importantly, $* also ignores quotes in your argument list. For example, if you had supplied ./foo.sh arg1 "arg2 arg3", even then:
./bar.sh "$*" => bar.sh will still receive arg2 and arg3 as separate parameters!
./bar.sh "$#" => will pass arg2 arg3 as a single parameter (which is what you usually want).
Notice again that this difference occurs only if you put $* and $# in quotes. Otherwise they have the same behaviour.
Official documentation: http://www.gnu.org/software/bash/manual/bash.html#Special-Parameters
Aside from the difference as described in the technical documents, it is best shown using some examples:
Lets assume we have four shell scripts, test1.sh:
#!/bin/bash
rm $*
test2.sh:
#!/bin/bash
rm "$*"
test3.sh:
#!/bin/bash
rm $#
test4.sh:
#!/bin/bash
rm "$#"
(I am using rm here instead of echo, because with echo, one can not see the difference)
We call all of them with the following commandline, in a directory which is otherwise empty:
./testX.sh "Hello World" Foo Bar
For test1.sh and test3.sh, we receive the following output:
rm: cannot remove ‘Hello’: No such file or directory
rm: cannot remove ‘World’: No such file or directory
rm: cannot remove ‘Foo’: No such file or directory
rm: cannot remove ‘Bar’: No such file or directory
This means, the arguments are taken as a whole string, joined with spaces, and then reparsed as arguments and passed to the command. This is generally not helpful when forwarding arguments to another command.
With test2.sh, we get:
rm: cannot remove ‘Hello World Foo Bar’: No such file or directory
So we have the same as for test{1,3}.sh, but this time, the result is passed as one argument.
test4.sh has something new:
rm: cannot remove ‘Hello World’: No such file or directory
rm: cannot remove ‘Foo’: No such file or directory
rm: cannot remove ‘Bar’: No such file or directory
This implies that the arguments are passed in a manner equivalent to how they were passed to the the script. This is helpful when passing arguments to other commands.
The difference is subtle, but will bite you when passing arguments to commands which expect information at certain points in the command line and when spaces take part in the game. This is in fact a good example of one of the many pitfalls of most shells.
see this here :
$# Stores the number of command-line arguments that
were passed to the shell program.
$? Stores the exit value of the last command that was
executed.
$0 Stores the first word of the entered command (the
name of the shell program).
$* Stores all the arguments that were entered on the
command line ($1 $2 ...).
"$#" Stores all the arguments that were entered
on the command line, individually quoted ("$1" "$2" ...).
take an example
./command -yes -no /home/username
so now..
$# = 3
$* = -yes -no /home/username
$# = ("-yes" "-no" "/home/username")
$0 = ./command
$1 = -yes
$2 = -no
$3 = /home/username
They are different when quoted:
$ set "a b" c d
$ echo $#
3
$ set "$*"
$ echo $#
1
$ set "a b" c d
$ echo $#
3
$ set "$#"
$ echo $#
3
Here only the second form preserves the argument count.