Unable to execute printf command by using formatter and argument variable - bash

I am trying to store all the printf formatter and arguments into their own respective variables to be execute later. Example code:
var="abc123"
var2="def 456"
printfArgument=$var" "$var2
formatter="%-10s"
formatter2="%-10s"
printfFormatter=$formatter" "$formatter2"\n"
printf "$printfFormatter" $printfArgument
output:
abc123 def
456
It seems like the space in var2 causes the 456 to display improperly. Any way to fix it?

You're correct; the space in var2 is being used for word-splitting. printf is receiving 3 arguments after the format string: abc123, def, and 456. The first two fill the two format specifiers for the first line of output. Since there is a remaining argument, the format string is used again to produce the second line of output.
You need to use an array for printfArgument:
printfArgument=( "$var" "$var2" )
printf "$printfFormatter" "${printfArgument[#]}"
or just use var and var2 separately:
printf "$printfFormatter" "$var" "$var2"

Related

Split String by Double Back Slashes in Bash

I am trying to split the string by double back slashes in bash and somehow it's not working.
My string is as follow:
abc\\xyz
My Code:
my_str='abc\\xyz'
IFS='\\' read -r -a arr <<< "$my_str"
echo "${#arr[#]}"
I am expecting that the output would be '2' as the length of the array would be 2.
However, I get 3 as a result and when I try to print the array values, I only get 'abc', and the second index remains empty. It seems like something is wrong with my code but I am unable to identify it.
Can anyone please help to resolve the issue?
If there are no spaces in your string you could use bash pattern substitutions to replace pairs of backslashes by a space and assign the result to an indexed array:
$ my_str='abc\\xyz\uvw\\rst\\\012\\\\345'
$ declare -a arr=( ${my_str//\\\\/ } )
$ echo "${#arr[#]}"
5
$ printf '%s\n' "${arr[#]}"
abc
xyz\uvw
rst
\012
345
Perhaps you could try to replace the backslashes on the string fist as showcased in this previous question. However, this would be inefficient for very large strings.
A slightly different take -
$: my_str='abc\\123\\ghi\\\012\\jkl\\foo bar\\\\xyz'
$: IFS='|' read -r -a arr <<< "${my_str//\\\\/\|}"
$: printf "[%s]\n" "${arr[#]}"
[abc]
[123]
[ghi]
[\012]
[jkl]
[foo bar]
[]
[xyz]

Arithmetic operations using numbers from grep

I have FILE from which I can extract two numbers using grep. The numbers appear in the last column.
$ grep number FILE
number1: 123
number2: 456
I would like to assign the numbers to variables, e.g. $num1 and $num2, and do some arithmetic operations using the variables.
How can I do this using bash commands?
Assumptions:
we want to match on lines that start with the string number
we will always find 2 matches for ^number from the input file
not interested in storing values in an array
Sample data:
$ cat file.dat
number1: 123
not a number: abc
number: 456
We'll use awk to find the desired values and print all to a single line of output:
$ awk '/^number/ { printf "%s ",$2 }' file.dat
123 456
From here we can use read to load the variables:
$ read -r num1 num2 < <(awk '/^number/ { printf "%s ",$2 }' file.dat)
$ typeset -p num1 num2
declare -- num1="123"
declare -- num2="456"
$ echo ".${num1}.${num2}."
.123.456.
NOTE: periods added as visual delimiters
Firstly, you need to extract the numbers from the file. Assuming that the file is always in the format stated, then you can use a while loop, combined with the the read command to read the numbers into a named variable, one row at a time.
You can then use the $(( )) operator to perform integer arithmetic to keep a running total of the incoming numbers.
For example:
#!/bin/bash
declare -i total=0 # -i declares an integer.
while read discard number; do # read returns false at EOF. discard is ignored.
total=$((total+number)) # Variables don't need '$' prefix in this case.
done < FILE # while loop passes STDIN to the 'read' command.
echo "Total is: ${total}"

Display First and Last string entries stored in a variable

I have a variable MyVar with values stored in it. For example:
MyVar="123, 234, 345, 456"
Each entry in the variable is separated by a coma as in the example above.
I want to be able to pick the first and last entry from this variable, i.e 123 and 456 respectively.
Any idea how I can achieve this from the command prompt terminal ?
Thanks!
Using bash substring removal:
$ echo ${MyVar##*,}
456
$ echo ${MyVar%%,*}
123
Also:
$ echo ${MyVar/,*,/,}
123, 456
More for example here:
https://tldp.org/LDP/abs/html/parameter-substitution.html
Edit: Above kind of expects the substrings to be separated by commas only. See comments where #costaparas gloriously demonstrates a case with , .
Try using sed:
MyVar="123, 234, 345, 456"
first=$(echo "$MyVar" | sed 's/,.*//')
last=$(echo "$MyVar" | sed 's/.*, //')
echo $first $last
Explanation:
To obtain the first string, we replace everything after & including
the first comma with nothing (empty string).
To obtain the last string, we replace everything before & including the last comma with nothing (empty string).
Using bash array:
IFS=', ' arr=($MyVar)
echo ${arr[0]} ${arr[-1]}
Where ${arr[0]} and ${arr[-1]} are your first and last respective values. Negative index requires bash 4.2 or later.
You could try following also with latest BASH version, by sending variable values into an array and then retrieve first and last element, keeping all either values in it saved in case you need them later in program etc.
IFS=', ' read -r -a array <<< "$MyVar"
echo "${array[0]}"
123
echo "${array[-1]}"
456
Awk alternative:
awk -F "(, )" '{ print $1" - "$NF }' <<< $MyVar
Set the field separator to command and a space. Print the first field and the last field (NF) with " - " in between.

How to iterate over text file having multiple-words-per-line using shell script?

I know how to iterate over lines of text when the text file has contents as below:
abc
pqr
xyz
However, what if the contents of my text file are as below,
abc xyz
cdf pqr
lmn rst
and I need to get values "abc" stored to one variable and"xyz" stored to another variable. How would I do that?
read splits the line by $IFS as many times as you pass variables to it:
while read var1 var2 ; do
echo "var1: ${var1} var2: ${var2}"
done
You see, if you pass var1 and var2 both columns go to separate variables. But note that if the line would contain more columns var2 would contain the whole remaining line, not just column2.
Type help read for more info.
If the delimiter is a space then you can do:
#!/bin/bash
ALLVALUES=()
while read line
do
ALLVALUES+=( $line )
done < "/path/to/your/file"
So after, you can just reference an element by ${ALLVALUES[0]} or ${ALLVALUES[1]} etc
If you want to read every word in a file into a single array you can do it like this:
arr=()
while read -r -a _a; do
arr+=("${a[#]}")
done < infile
Which uses -r to avoid read from interpreting backslashes in the input and -a to have it split the words (splitting on $IFS) into an array. It then appends all the elements of that array to the accumulating array while being safe for globbing and other metacharacters.
This awk command reads the input word by word:
awk -v RS='[[:space:]]+' '1' file
abc
xyz
cdf
pqr
lmn
rst
To populate a shell array use awk command in process substitution:
arr=()
while read -r w; do
arr+=("$w")
done < <(awk -v RS='[[:space:]]+' '1' file)
And print the array content:
declare -p arr
declare -a arr='([0]="abc" [1]="xyz" [2]="cdf" [3]="pqr" [4]="lmn" [5]="rst")'

Bash Columns SED and BASH Commands without AWK?

I wrote 2 difference scripts but I am stuck at the same problem.
The problem is am making a table from a file ($2) that I get in args and $1 is the numbers of columns. A little bit hard to explain but I am gonna show you input and output.
The problem is now that I don't know how I can save every column now in a difference var so i can build it in my HTML code later
#printf #TR##TD#$...#/TD##TD#$...#/TD##TD#$..#/TD##/TR##TD#$...
so input look like that :
Name\tSize\tType\tprobe
bla\t4711\tfile\t888888888
abcde\t4096\tdirectory\t5555
eeeee\t333333\tblock\t6666
aaaaaa\t111111\tpackage\t7777
sssss\t44444\tfile\t8888
bbbbb\t22222\tfolder\t9999
Code :
c=1
column=$1
file=$2
echo "$( < $file)"| while read Line ; do
Name=$(sed "s/\\\t/ /g" $file | cut -d' ' -f$c,-$column)
printf "$Name \n"
#let c=c+1
#printf "<TR><TD>$Name</TD><TD>$Size</TD><TD>$Type</TD></TR>\n"
exit 0
done
Output:
Name Size Type probe
bla 4711 file 888888888
abcde 4096 directory 5555
eeeee 333333 block 6666
aaaaaa 111111 package 7777
sssss 44444 file 8888
bbbbb 22222 folder 9999
This is tailor-made job for awk. See this script:
awk -F'\t' '{printf "<tr>";for(i=1;i<=NF;i++) printf "<td>%s</td>", $i;print "</tr>"}' input
<tr><td>bla</td><td>4711</td><td>file</td><td>888888888</td></tr>
<tr><td>abcde</td><td>4096</td><td>directory</td><td>5555</td></tr>
<tr><td>eeeee</td><td>333333</td><td>block</td><td>6666</td></tr>
<tr><td>aaaaaa</td><td>111111</td><td>package</td><td>7777</td></tr>
<tr><td>sssss</td><td>44444</td><td>file</td><td>8888</td></tr>
<tr><td>bbbbb</td><td>22222</td><td>folder</td><td>9999</td></tr>
In bash:
celltype=th
while IFS=$'\t' read -a columns; do
rowcontents=$( printf '<%s>%s</%s>' "$celltype" "${columns[#]}" "$celltype" )
printf '<tr>%s</tr>\n' "$rowcontents"
celltype=td
done < <( sed $'s/\\\\t/\t/g' "$2")
Some explanations:
IFS=$'\t' read -a columns reads a line from standard input, using only the tab character to separate fields, and putting each field into a separate element of the array columns. We change IFS so that other whitespace, which could occur in a field, is not treated as a field delimiter.
On the first line read from standard input, <th> elements will be output by the printf line. After resetting the value of celltype at the end of the loop body, all subsequent rows will consist of <td> elements.
When setting the value of rowcontents, take advantage of the fact that the first argument is repeated as many times as necessary to consume all the arguments.
Input is via process substitution from the sed command, which requires a crazy amount of quoting. First, the entire argument is quoted with $'...', which tells bash to replace escaped characters. bash converts this to the literal string s/\\t/^T/g, where I am using ^T to represent a literal ASCII 09 tab character. When sed sees this argument, it performs its own escape replacement, so the search text is a literal backslash followed by a literal t, to be replaced by a literal tab character.
The first argument, the column count, is unnecessary and is ignored.
Normally, you avoid making the while loop part of a pipeline because you set parameters in the loop that you want to use later. Here, all the variables are truly local to the while loop, so you could avoid the process substitution and use a pipeline if you wish:
sed $'s/\\\\t/\t/g' "$2" | while IFS=$'\t' read -a columns; do
...
done

Resources