turn off bash variable substitution - linux

function ctrace {
echo "+ $#"
"$#"
}
ctrace echo "hi"
How would I get this function to output (with quotes):
echo "hi"
In this version the quotes are lost echo hi... Here is another example:
a=b
ctrace echo $a
This should output echo $a instead of echo b

The problem is not the function, but the caller.
In the first case the quotes are stripped out before the function gets the parameters. In the second, $a substitution is done before it gets to the function.
Try:
ctrace 'echo "hi"'
ctrace 'echo $a'

You need to enclose the string within single quotes
ctrace 'echo "hi"'
ctrace 'echo $a'

Related

Difference between "&&" inside variable and outside variable in shell script

Just out of curiosity:
A="echo hi"
B="echo hello"
C="$A && $B"
echo $C // prints "echo hi && echo hello"
$C
echo "$A && $B" // prints "echo hi && echo hello"
$A && $B
I though this would work in a same manner. But it shows different results as this:
echo hi && echo hello
hi && echo hello
echo hi && echo hello
hi
hello
Why?
Very Short Answer
Read BashFAQ #50.
Slightly Short Answer
Variable expansions do not go through all parsing steps (indeed, if they did, it would be impossible to write scripts securely processing untrusted data in bash). Unquoted expansions go through string-splitting and glob expansion only -- these stages split strings into pieces, and evaluate those pieces as globs, but don't recognize any of those pieces as syntax. Quoted expansions are substituted literally, meaning they stay strings, period.
Thus:
echo $C # runs: "echo" "echo " "hi" "&&" "echo" "hello"
$C # runs: "echo " "hi" "&&" "echo" "hello"
echo "$A && $B" # runs: "echo" "echo hi && echo hello"
$A && $B # runs: "echo" "hi" && "echo" "hello"
Notice how in the last example, the && wasn't quoted? That represents that it was parsed as syntax, not data. As syntax, && acts as a command separator (whereas as data, it just gets passed to echo).

bash separate parameters with specific delimiter

I am searching for a command, that separates all given parameters with a specific delimiter, and outputs them quoted.
Example (delimiter is set to be a colon :):
somecommand "this is" "a" test
should output
"this is":"a":"test"
I'm aware that the shell interprets the "" quotes before passing the parameters to the command. So what the command should actually do is to print out every given parameter in quotes and separate all these with a colon.
I'm also not seeking for a bash-only solution, but for the most elegant solution.
It is very easy to just loop over an array of these elements and do that, but the problem is that I have to use this inside a gnu makefile which only allows single line shell commands and uses sh instead of bash.
So the simpler the better.
How about
somecommand () {
printf '"%s"\n' "$#" | paste -s -d :
}
Use printf to add the quotes and print every entry on a separate line, then use paste with the -s ("serial") option and a colon as the delimiter.
Can be called like this:
$ somecommand "this is" "a" test
"this is":"a":"test"
apply_delimiter () {
(( $# )) || return
local res
printf -v res '"%s":' "$#"
printf '%s\n' "${res%:}"
}
Usage example:
$ apply_delimiter hello world "how are you"
"hello":"world":"how are you"
As indicated in a number of the comments, a simple "loop-over" approach, looping over each of the strings passed as arguments is a fairly straight-forward way to approach it:
delimit_colon() {
local first=1
for i in "$#"; do
if [ "$first" -eq 1 ]; then
printf "%s" "$i"
first=0
else
printf ":%s" "$i"
fi
done
printf "\n"
}
Which when combined with a short test script could be:
#!/bin/bash
delimit_colon() {
local first=1
for i in "$#"; do
if [ "$first" -eq 1 ]; then
printf "%s" "$i"
first=0
else
printf ":%s" "$i"
fi
done
printf "\n"
}
[ -z "$1" ] && { ## validate input
printf "error: insufficient input\n"
exit 1
}
delimit_colon "$#"
exit 0
Test Input/Output
$ bash delimitargs.sh "this is" "a" test
this is:a:test
Here a solution using the z-shell:
#!/usr/bin/zsh
# this is "somecommand"
echo '"'${(j_":"_)#}'"'
If you have them in an array already, you can use this command
MYARRAY=("this is" "a" "test")
joined_string=$(IFS=:; echo "$(MYARRAY[*])")
echo $joined_string
Setting the IFS (internal field separator) will be the character separator. Using echo on the array will display the array using the newly set IFS. Putting those commands in $() will put the output of the echo into joined_string.

What does ${1} and ${*} do? [duplicate]

This question already has answers here:
What are the special dollar sign shell variables?
(4 answers)
Closed 3 years ago.
This Nagios script uses ${1} and ${*} like so
if [ "${1}" ]; then
if [ "${ERRORSTRING}" ]; then
echo "${ERRORSTRING} ${OKSTRING}" | sed s/"^\/ "// | mail -s "$(hostname -s): ${0} reports errors\
" -E ${*}
fi
else
if [ "${ERRORSTRING}" -o "${OKSTRING}" ]; then
echo "${ERRORSTRING} ${OKSTRING}" | sed s/"^\/ "//
exit ${ERR}
else
echo no zpool volumes found
exit 3
fi
fi
Question
What does ${1} and ${*} do?
The command-line arguments $1, $2, $3,...$9 are positional parameters, with $0 pointing to the actual command, program, shell script, or function and $1, $2, $3, ...$9 as the arguments to the command.
"$*" special parameter takes the entire list as one argument with spaces between and the "$#" special parameter takes the entire list and separates it into separate arguments.
Suppose test.sh given below:
#!/bin/sh
echo "File Name: $0"
echo "First Parameter : $1"
echo "First Parameter : $2"
echo "Quoted Values: $#"
echo "Quoted Values: $*"
echo "Total Number of Parameters : $#"
Quoting from Special Parameters in the manual:
*
Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word
with the value of each parameter separated by the first character of
the IFS special variable. That is, "$*" is equivalent to "$1c$2c…",
where c is the first character of the value of the IFS variable. If
IFS is unset, the parameters are separated by spaces. If IFS is null,
the parameters are joined without intervening separators.
$1 refers to a Positional Parameter.
Assume the following script:
echo "${1}"
echo "${*}"
Upon invoking by saying:
bash scriptname foo bar baz
it would produce:
foo
foo bar baz
Now observe the effect of the variable IFS. Given the script:
IFS=
echo "${1}"
echo "${*}"
echo "${#}"
Invoking it by saying:
bash scriptname "foo:hey you" bar baz
would produce:
foo:hey you
foo:hey youbarbaz
foo:hey you bar baz
$1 is used(to display or to get input for user interactively) to display First parameter.
$* is used to show all parameters entered.
# cat schecking.sh
#!/bin/bash
echo "All values:" "$*"
echo "Total number of Parameter(s) :" "$#"
# ./schecking.sh
All values:
Total number of Parameter(s) : 0
# ./schecking.sh It will help us to check
All values: It will help us to check
Total number of Parameter(s) : 6
# cat schecking.sh
#!/bin/bash
echo "First value is:" $1
echo "All values:" "$*"
echo "Total number of Parameter(s) :" "$#"
# ./schecking.sh It will help us to check
First value is: It
All values: It will help us to check
Total number of Parameter(s) : 6

How to catch missing argument in function mistake?

I have just made the following mistake, where I am passing an argument to a function which is empty.
var1="ok"
var2=$notDefined
func $var1 $var2
func() {
var1=$1
var2=$2
echo $var1
echo $var2
}
For each argument in the function I could do
if [ -z $1 ]; then echo "Empty argument"; fi
But is there a more generic method to do this, so it is easy reusable, and would perhaps even tell the variable name that is empty?
You can stop whole script by set -u. It will fail if you try to use unset variable. It is very general approach.
Bash will output following localized message to standard error:
bash: x: unbound variable
You want to use the ? bash variable substitution operator:
var1=${1:?"undefined!"}
If $1 exists and isn't null, var1 is set to its value, otherwise bash prints 1 followed by "undefined!" and aborts the current command or script. This syntax can used for any bash variable.
In your case the empty variables are created, because there are too few arguments to the function.
You can get the number of passed arguments via $#. All variables that use $n with a higher number n must then be empty. You could check for a sufficiently high number of arguments at the beginning of your function.
#!/bin/bash
var1="ok"
var2=$notDefined
func() {
if [[ $# -ge 2 ]]; then
var1=$1
var2=$2
echo $var1
echo $var2
else
echo "Missing values"
fi
}
func $var1 $var2
Here it is running
./test.sh
Missing values
Here it is with two values:
#!/bin/bash
var1="ok"
var2="dokie"
func() {
if [[ $# -ge 2 ]]; then
var1=$1
var2=$2
echo $var1
echo $var2
else
echo "Missing values"
fi
}
func $var1 $var2
Results with:
./test.sh
ok
dokie

Get the executed command, quoted params, after executing `"${argv[#]}"`

This function works:
source foo.bash && foo -n "a b c.txt"
The problem is, no matter what I've tried, I couldn't get the last line echo "$CMD" (or echo $CMD) to generate exactly this output:
cat -n "a b c.txt"
How to achieve that?
# foo.bash
function foo() {
local argv=("$#");
local OUT=`cat "${argv[#]}"`
local CMD=`echo cat "${argv[#]}"`
echo "--------------------------"
echo "$OUT"
echo "--------------------------"
echo "$CMD"
}
The output is instead:
cat -n a b c.txt
With this command: foo -n \"a b c.txt\" it does work for the display of the command, but it gives errors for the execution via the backtick.
The file "a b c.txt" is a valid, small, text file.
You need to escape quotes inside of the assignment:
local CMD="cat \"${argv[#]}\""
Also, echo is not needed to concatenate strings.
There you go, with the help of number of tokens in bash variable I've come up with the right solution.
I've almost forgot WHY we actually need quoting for one argument, it's because it has multiple words!
function foo() {
local argv=( "$#" );
local OUT=`cat "${argv[#]}"`
echo "--------------------------"
echo "$OUT"
echo "--------------------------"
local CMD="cat"
for word in "${argv[#]}"; do
words="${word//[^\ ]} "
if [[ ${#words} > 1 ]]; then
local CMD="$CMD \"${word}\""
else
local CMD="$CMD $word"
fi
done
echo "$CMD"
}
Hope it helps someone.

Resources