Create a bash script inside a bash script that uses special variables $1, $# - bash

I'm trying to create a script that creates an other script that uses $1 and $#, the problem is that those variables are being interpreted by the first script, so they are empty. Here's my problem, the first script creates the script /tmp/test.sh
#!/bin/bash
cat << EOF > /tmp/test.sh
#!/bin/bash
echo $1
echo $#
EOF
The result in /tmp/test.sh:
#!/bin/bash
echo
echo 0
Does anyone know how to avoid this and get in /tmp/test.sh $1 and $#?
I would like to have in /tmp/test.sh:
#!/bin/bash
echo $1
echo $#
Thanks in advance.

Quote the here-document delimiter so that the contents of the here document are treated as literal text (i.e., as if occurring in a single-quoted string).
cat << 'EOF' > /tmp/test.sh
#!/bin/bash
echo $1
echo $#
EOF
Any quoting will work, not just single quotes. The only important thing is that at least one character be escaped.
cat << \EOF
cat << "EOF"
cat << E"O"F
etc

Related

BASH text edit with seq

With this I can callmyscrip.sh 100 and this will print 100 rows with the content generated by seq, but what's the best way to separate the content TEXT="xxx yyy ${this}" for readability with a variable?
#!/bin/bash
howmanytimes=$1
for this in $(seq -w ${howmanytimes}); do echo " /
-- ${this}
"; done
this instead would not work as $this isn't replaced:
#!/bin/bash
howmanytimes=$1
TEXT="THIS WOULD NOT WORK: ${this}"
for this in $(seq -w ${howmanytimes}); do echo ${TEXT} ; done
export $TEXT
seq(1) is nonstandard, inefficient and useless.
Check http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Conditional_Loops
With ksh:
#!/bin/ksh
txt='this should work with int: '
for i in {0..$1}; do
echo "$txt $i"
done
With bash:
#!/bin/bash
txt='this should work with int: '
for ((i=0; i<=$1; i++)) {
echo "$txt $i"
}
You can wrap your dynamic text in a bash function:
#!/bin/bash
get_content() {
echo "THIS WOULD WORK: $1"
}
how_many_times=$1
for i in $(seq -w ${how_many_times}); do
echo "$(get_content $i)"
done
If you just need to output the content, can simplify it like this:
#!/bin/bash
get_content() {
echo "THIS WOULD WORK: $1"
}
how_many_times=$1
for i in $(seq -w ${how_many_times}); do
get_content $i
done
Check your script with shellcheck. printf is a simple template language. I could see:
#!/bin/bash
howmanytimes=$1
text="THIS WOULD WORK: %s"
for this in $(seq -w "${howmanytimes}"); do
printf "$text" "$this"
done
You could use envsubst to replace environment, however in this case printf looks way clearer. Research quoting in shell.
#!/bin/bash
howmanytimes=$1
text='THIS WOULD WORK: ${THIS}'
for this in $(seq -w "${howmanytimes}"); do
THIS="$this" envsubst <<<"$text"
done
You can use printf directly, and skip the loop entirely:
#!/bin/bash
howmanytimes=$1
text="This WILL work: %s"
printf "${text}\n" $(seq -w ${howmanytimes})
Note that \n needs to be added to the format string, since printf doesn't add a newline automatically like echo does. If you want additional newlines (like in the example), you can add them as either \n or actual newlines, in either the format variable or where it's used in the printf argument. Also, if you want to include a literal backslash or percent sign in the string, double it (i.e. %% to print %, or \\ to print \).
BTW, since printf is a bash builtin, it's not subject to the normal argument list length limits, so this'll work even with very large numbers of numbers.

Loop inside "heredoc" in shell scripting

I need to execute series of commands inside an interactive program/utility with parameterized values. Is there a way to loop inside heredoc ? Like below .. Not sure if eval can be of any help here. Below example doesn't seem to work as the interactive doesn't seem to recognize system commands.
#!/bin/sh
list="OBJECT1 OBJECT2 OBJECT3"
utilityExecutable << EOF
for i in $list ; do
utilityCommand $i
done
EOF
Instead of passing a here-document to utilityExecutable,
the equivalent is to pipe the required text to it. You can create the desired text using echo statements in a for-loop, and pipe the entire loop output to utilityExecutable:
#!/bin/sh
list="OBJECT1 OBJECT2 OBJECT3"
for i in $list; do
echo "utilityCommand $i"
done | utilityExecutable
Yes, this is tricky and can be confusing! You have to modify your codes as follow.
#!/bin/sh
list="OBJECT1 OBJECT2 OBJECT3"
utilityExecutable << EOF
list="$list"
for i in \$list ; do
utilityCommand \$i
done
EOF
This is because heredoc uses its own variables, which are completely separate from the shell. When you are inside heredoc, you have to use and modify heredoc's own variables. So the \$ is needed to reference heredoc's own variables instead of shell variables when inside heredoc.
cat << EOF
$(
for i in {1..10}; do
echo $i;
done
)
EOF
commandxyz -noenv<<EOF
echo "INFO - Inside eof"
t_files=("${p_files[#]}")
#copy array
#echo \${t_files[*]}
#all elements from array
#echo \${#t_files[#]}
#array length
for i in \${t_files[#]} ; do
echo -e \$i;
do other stuff \$i;
done
cat $patch_file
git apply $patch_file
EOF
myVar=$(
for i in {1..5}; do
echo hello;
echo world;
done;
); cat <<< $myVar

keeping the bash parameters array intack

I have a bash script
fooA
#!/bin/bash
script_name=$1;
script_params=$( echo $# | awk '{ $1=""; print $0 }' );
bash /path/to/scripts/$script_name $script_params > /dev/stdout;
and another script fooB in the .../scripts/ directory.
#!/bin/bash
echo 1. $1
echo 2. $2
My plan is simple:
fooA fooB "some sentence 1" "some sentence 2"
should produce:
some sentence 1
some sentence 2
Using my current script, I would get
some
sentence
Because the double quotes are not preserved when calling fooB from fooA.
Keeping in mind that there many other scripts in the .../scripts directory, how would I change the script_params=$(...) line in fooA file to preserve variables when calling other scripts.
#jm666's answer will work fine if there are no additional constraints. For completeness, though, I'll give a version that doesn't mess with the first script's argument list:
#/bin/bash
script_name="$1"
script_params=( "${#:2}" )
bash /path/to/scripts/"$script_name" "${script_params[#]}" > /dev/stdout
Or you can skip the variables entirely:
#/bin/bash
bash /path/to/scripts/"$1" "${#:2}" > /dev/stdout
#!/bin/bash
name="$1"
shift
"/path/to/script/$name" "$#"

Passing arguments to a command in Bash script with spaces

I'm trying to pass 2 arguments to a command and each argument contains spaces, I've tried escaping the spaces in the args, I've tried wrapping in single quotes, I've tried escaping \" but nothing will work.
Here's a simple example.
#!/bin/bash -xv
ARG="/tmp/a b/1.txt"
ARG2="/tmp/a b/2.txt"
ARG_BOTH="\"$ARG\" \"$ARG2\""
cat $ARG_BOTH
I'm getting the following when it runs:
ARG_BOTH="$ARG $ARG2"
+ ARG_BOTH='/tmp/a\ b/1.txt /tmp/a\ b/2.txt'
cat $ARG_BOTH
+ cat '/tmp/a\' b/1.txt '/tmp/a\' b/2.txt
cat: /tmp/a\: No such file or directory
cat: b/1.txt: No such file or directory
cat: /tmp/a\: No such file or directory
cat: b/2.txt: No such file or directory
See http://mywiki.wooledge.org/BashFAQ/050
TLDR
Put your args in an array and call your program as myutil "${arr[#]}"
#!/bin/bash -xv
file1="file with spaces 1"
file2="file with spaces 2"
echo "foo" > "$file1"
echo "bar" > "$file2"
arr=("$file1" "$file2")
cat "${arr[#]}"
Output
file1="file with spaces 1"
+ file1='file with spaces 1'
file2="file with spaces 2"
+ file2='file with spaces 2'
echo "foo" > "$file1"
+ echo foo
echo "bar" > "$file2"
+ echo bar
arr=("$file1" "$file2")
+ arr=("$file1" "$file2")
cat "${arr[#]}"
+ cat 'file with spaces 1' 'file with spaces 2'
foo
bar
This might be a good use-case for the generic "set" command, which sets the top-level shell parameters to a word list. That is, $1, $2, ... and so also $* and $# get reset.
This gives you some of the advantages of arrays while also staying all-Posix-shell-compatible.
So:
set "arg with spaces" "another thing with spaces"
cat "$#"
The most straightforward revision of your example shell script that will work correctly is:
#! /bin/sh
ARG="/tmp/a b/1.txt"
ARG2="/tmp/a b/2.txt"
cat "$ARG" "$ARG2"
However, if you need to wrap up a whole bunch of arguments in one shell variable, you're up a creek; there is no portable, reliable way to do it. (Arrays are Bash-specific; the only portable options are set and eval, both of which are asking for grief.) I would consider a need for this as an indication that it was time to rewrite in a more powerful scripting language, e.g. Perl or Python.

Indirect parameter substitution in shell script

I'm having a problem with a shell script (POSIX shell under HP-UX, FWIW). I have a function called print_arg into which I'm passing the name of a parameter as $1. Given the name of the parameter, I then want to print the name and the value of that parameter. However, I keep getting an error. Here's an example of what I'm trying to do:
#!/usr/bin/sh
function print_arg
{
# $1 holds the name of the argument to be shown
arg=$1
# The following line errors off with
# ./test_print.sh[9]: argval=${"$arg"}: The specified substitution is not valid for this command.
argval=${"$arg"}
if [[ $argval != '' ]] ; then
printf "ftp_func: $arg='$argval'\n"
fi
}
COMMAND="XYZ"
print_arg "COMMAND"
I've tried re-writing the offending line every way I can think of. I've consulted the local oracles. I've checked the online "BASH Scripting Guide". And I sharpened up the ol' wavy-bladed knife and scrubbed the altar until it gleamed, but then I discovered that our local supply of virgins has been cut down to, like, nothin'. Drat!
Any advice regarding how to get the value of a parameter whose name is passed into a function as a parameter will be received appreciatively.
You could use eval, though using direct indirection as suggested by SiegeX is probably nicer if you can use bash.
#!/bin/sh
foo=bar
print_arg () {
arg=$1
eval argval=\"\$$arg\"
echo "$argval"
}
print_arg foo
In bash (but not in other sh implementations), indirection is done by: ${!arg}
Input
foo=bar
bar=baz
echo $foo
echo ${!foo}
Output
bar
baz
This worked surprisingly well:
#!/bin/sh
foo=bar
print_arg () {
local line name value
set | \
while read line; do
name=${line%=*} value=${line#*=\'}
if [ "$name" = "$1" ]; then
echo ${value%\'}
fi
done
}
print_arg foo
It has all the POSIX clunkiness, in Bash would be much sorter, but then again, you won't need it because you have ${!}. This -in case it proves solid- would have the advantage of using only builtins and no eval. If I were to construct this function using an external command, it would have to be sed. Would obviate the need for the read loop and the substitutions. Mind that asking for indirections in POSIX without eval, has to be paid with clunkiness! So don't beat me!
Even though the answer's already accepted, here's another method for those who need to preserve newlines and special characters like Escape ( \033 ): Storing the variable in base64.
You need: bc, wc, echo, tail, tr, uuencode, uudecode
Example
#!/bin/sh
#====== Definition =======#
varA="a
b
c"
# uuencode the variable
varB="`echo "$varA" | uuencode -m -`"
# Skip the first line of the uuencode output.
varB="`NUM=\`(echo "$varB"|wc -l|tr -d "\n"; echo -1)|bc \`; echo "$varB" | tail -n $NUM)`"
#====== Access =======#
namevar1=varB
namevar2=varA
echo simple eval:
eval "echo \$$namevar2"
echo simple echo:
echo $varB
echo precise echo:
echo "$varB"
echo echo of base64
eval "echo \$$namevar1"
echo echo of base64 - with updated newlines
eval "echo \$$namevar1 | tr ' ' '\n'"
echo echo of un-based, using sh instead of eval (but could be made with eval, too)
export $namevar1
sh -c "(echo 'begin-base64 644 -'; echo \$$namevar1 | tr ' ' '\n' )|uudecode"
Result
simple eval:
a b c
simple echo:
YQpiCmMK ====
precise echo:
YQpiCmMK
====
echo of base64
YQpiCmMK ====
echo of base64 - with updated newlines
YQpiCmMK
====
echo of un-based, using sh instead of eval (but could be made with eval, too)
a
b
c
Alternative
You also could use the set command and parse it's output; with that, you don't need to treat the variable in a special way before it's accessed.
A safer solution with eval:
v=1
valid_var_name='[[:alpha:]_][[:alnum:]_]*$'
print_arg() {
local arg=$1
if ! expr "$arg" : "$valid_var_name" >/dev/null; then
echo "$0: invalid variable name ($arg)" >&2
exit 1
fi
local argval
eval argval=\$$arg
echo "$argval"
}
print_arg v
print_arg 'v; echo test'
Inspired by the following answer.

Resources