BASH text edit with seq - bash

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.

Related

Echo multiple variables with delimiter and prevent new line (in code) being printed in the output

I would like to print a number of variables in a tab-delimited manor, and for the sake of code cleanliness, I would like it write it like the below:
for f in files*
do
echo -e "
$var1\t
$var2\t
$var3\t
$var4\t
"
done
output:
file1_var1 file1_var2 file1_var3 file1_var4
file2_var1 file2_var2 file2_var3 file2_var4
I can get it to work like this, but it is not very tidy:
echo -e "$var1\t$var2\t$var3\t$var4\t"
I have tried using something like this to replace the new lines with tabs, but it seems a bit cumbersome. Also I need to add a new line at the end to prevent multiple files being printed on the same line, and it adds a new line between each output line as well.
for i in files*
do
echo -e "
$var1\t
$var2\t
$var3\t
$var4\t
| sed '$!{:a;N;s/\n/\t/;ta}'
"
echo -e "\n"
done
output:
file1_var1 file1_var2 file1_var3 file1_var4
file2_var1 file2_var2 file2_var3 file2_var4
Basically never use echo -e. You can almost always make it more elegant with printf, or simply cat
If you don't mind getting one spurious tab,
printf "%s\t" "$var1" "$var2" "$var3" "$var4"
printf '\n'
With a here document, you can write a simple sed script to replace all internal newlines with tabs.
sed 'N;N;N;s/\n/\t/g' <<:
$var1
$var2
$var3
$var4
:
(This is probably not entirely portable, but it works on MacOS and Ubuntu, so it should work most places you care about.)
Perhaps also look into pr, which can arrange things in a matrix.
printf "%s\n" "$var1" "$var2" "$var3" "$var4" | pr -bt4
(Again, probably not entirely portable, but works e.g. on Linux. On MacOS I had to remove the b option.)
It's not clear where the variables should come from; perhaps write a function to wrap printf if you just want to print its arguments in groups of four:
fourcols () {
while [ $# -gt 4 ]; do
fourcols "$1" "$2" "$3" "$4"
shift 4 # not portable to sh
done
while [ $# -gt 0 ]; do
printf "%s" "$1"
shift
if [ $# -gt 0 ]; then
printf "\t%s" "$#"
done
printf "\n"
done
}
Then you can just call fourcols * if you want to print all files in four columns, for example.
Defining a function that joins its arguments as a TSV record:
tsvrecord() { first=$1; shift && printf '%s' "$first" "${#/#/$'\t'}"; echo; }
tsvrecord "$var1" "$var2" "$var3" "$var4"
remark: no TSV escaping is done here, so if a filepath contains \t or \n (which is allowed on Unix) then the resulting TSV will be broken.

What is the POSIX shell equivalent of bash <<<

I have a variable that looks sort of like this:
msg="newton apple tree"
I want to assign each of these words into separate variables. This is easy to do in bash:
read a b c <<< $msg
Is there a compact, readable way to do this in POSIX shell?
A here string is just syntactic sugar for a single-line here document:
$ msg="foo * bar"
$ read a b c <<EOF
> $msg
> EOF
$ echo "$a"
foo
$ echo "$b"
*
$ echo "$c"
bar
To write idiomatic scripts, you can't just look at each individual syntax element and try to find a POSIX equivalent. That's like translating text by replacing each individual word with its entry in the dictionary.
The POSIX way of splitting a string known to have three words into three arguments, similar but not identical to read is:
var="newton apple tree"
set -f
set -- $var
set +f
a=$1 b=$2 c=$3
echo "$a was hit by an $b under a $c"
It's not pretty, but as a general-purpose solution, you can work around this with a named pipe.
From BashFAQ #24:
mkfifo mypipe
printf '%s\n' "$msg" >mypipe &
read -r a b c <mypipe
printf is more reliable / better-specified than echo; echo behavior varies between implementations if you have a message containing only, say, -E or -n.
That said, for what you're doing here, you could just use parameter expansion:
a=${msg%% *}; msg=${msg#* }
b=${msg%% *}; msg=${msg#* }
c=${msg%% *}; msg=${msg#* }

How can I get the length of all arguments given to a function in bash?

I'd like to get the length of the string that I get when i use "$*" in my function.
I tried:
echo ${#"$*"}
and
echo ${#"*"}
both gave me a bad substitution error.
I don't think you can do it in a single command. However, this seems to work:
#!/bin/bash
a="$#"
echo "${#a}"
Using a temporary variable is the only one basic way, and you need to unset IFS or set it to empty string to prevent spaces in between. And use $* not $# for it would give you spaces in between:
IFS= eval "__=\"\$*\""
echo "${#__}"
Another way is to loop through all strings:
L=0; for __; do (( L += ${#__} )); done
echo "$L"
You can use one of the following.
expr length "$*"
echo "$*" | awk '{print length}'
$# holds the number of positional parameters passed to the function.
Try it:
#!/bin/bash
t() {
echo "num args=$#"
echo "all $*"
}
t "$#"

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.

Loading variables from a text file into bash script

Is it possible to load new lines from a text file to variables in bash?
Text file looks like?
EXAMPLEfoo
EXAMPLEbar
EXAMPLE1
EXAMPLE2
EXAMPLE3
EXAMPLE4
Variables become
$1 = EXAMPLEfoo
$2 = EXAMPLEbar
ans so on?
$ s=$(<file)
$ set -- $s
$ echo $1
EXAMPLEfoo
$ echo $2
EXAMPLEbar
$ echo $#
EXAMPLEfoo EXAMPLEbar EXAMPLE1 EXAMPLE2 EXAMPLE3 EXAMPLE4
I would improve the above by getting rid of temporary variable s:
$ set -- $(<file)
And if you have as input a file like this
variable1 = value
variable2 = value
You can use following construct to get named variables.
input=`cat filename|grep -v "^#"|grep "\c"`
set -- $input
while [ $1 ]
do
eval $1=$3
shift 3
done
cat somefile.txt| xargs bash_command.sh
bash_command.sh will receive these lines as arguments
saveIFS="$IFS"
IFS=$'\n'
array=($(<file))
IFS="$saveIFS"
echo ${array[0]} # output: EXAMPLEfoo
echo ${array[1]} # output: EXAMPLEbar
for i in "${array[#]}"; do echo "$i"; done # iterate over the array
Edit:
The loop in your pastebin has a few problems. Here it is as you've posted it:
for i in "${array[#]}"; do echo " "AD"$count = "$i""; $((count=count+1)); done
Here it is as it should be:
for i in "${array[#]}"; do declare AD$count="$i"; ((count=count+1)); done
or
for i in "${array[#]}"; do declare AD$count="$i"; ((count++)); done
But why not use the array directly? You could call it AD instead of array and instead of accessing a variable called "AD4" you'd access an array element "${AD[4]}".
echo "${AD[4]}"
if [[ ${AD[9]} == "EXAMPLE value" ]]; then do_something; fi
This can be done be with an array if you don't require these variables as inputs to a script. push() function lifted from the Advanced Scripting Guide
push() # Push item on stack.
{
if [ -z "$1" ] # Nothing to push?
then
return
fi
let "SP += 1" # Bump stack pointer.
stack[$SP]=$1
return
}
The contents of /tmp/test
[root#x~]# cat /tmp/test
EXAMPLEfoo
EXAMPLEbar
EXAMPLE1
EXAMPLE2
EXAMPLE3
EXAMPLE4
SP=0; for i in `cat /tmp/test`; do push $i ; done
Then
[root#x~]# echo ${stack[3]}
EXAMPLE1
None of the above will work, if your values are quoted with spaces.
However, not everythinf is lost.
Try this:
eval "$(VBoxManage showvminfo "$VMname" --details --machinereadable | egrep "^(name|UUID|CfgFile|VMState)")"
echo "$name {$UUID} $VMState ($VMStateChangeTime) CfgFile=$CfgFile"
P.S.
Nothing will ever work, if your names are quoted or contain dashes.
If you have something like that, as is the case with VBoxManage output ("IDE-1-0"="emptydrive" and so on), either egrep only specific values, as shown in my example, or silence the errors.
However, silencing erors is always dangerous. You never know, when the next value will have unquoted "*" in it, thus you must treat values loaded this way very careful, with all due precaution.

Resources