In bash, how could I add integers with leading zeroes and maintain a specified buffer - bash

For example, I want to count from 001 to 100. Meaning the zero buffer would start off with 2, 1, then eventually 0 when it reaches 100 or more.
ex:
001
002
...
010
011
...
098
099
100
I could do this if the numbers had a predefined number of zeroes with printf "%02d" $i. But that's static and not dynamic and would not work in my example.

If by static versus dynamic you mean that you'd like to be able to use a variable for the width, you can do this:
$ padtowidth=3
$ for i in 0 {8..11} {98..101}; do printf "%0*d\n" $padtowidth $i; done
000
008
009
010
011
098
099
100
101
The asterisk is replaced by the value of the variable it corresponds to in the argument list ($padtowidth in this case).
Otherwise, the only reason your example doesn't work is that you use "2" (perhaps as if it were the maximum padding to apply) when it should be "3" (as in my example) since that value is the resulting total width (not the pad-only width).

If your system has it, try seq with the -w (--equal-width) option:
$ seq -s, -w 1 10
01,02,03,04,05,06,07,08,09,10
$ for i in `seq -w 95 105` ; do echo -n " $i" ; done
095 096 097 098 099 100 101 102 103 104 105

In Bash version 4 (use bash -version) you can use brace expansion. Putting a 0 before either limit forces the numbers to be padded by zeros
echo {01..100} # 001 002 003 ...
echo {03..100..3} # 003 006 009 ...

#!/bin/bash
max=100;
for ((i=1;i<=$max;i++)); do
printf "%0*d\n" ${#max} $i
done
The code above will auto-pad your numbers with the correct number of 0's based upon how many digits the max/terminal value contains. All you need to do is change the max variable and it will handle the rest.
Examples:
max=10
01
02
03
04
05
06
07
08
09
10
max=100
001
002
003
004
005
006
...
097
098
099
100
max=1000
0001
0002
0003
0004
0005
0006
...
0997
0998
0999
1000

# jot is available on FreeBSD, Mac OS X, ...
jot -s " " -w '%03d' 5
jot -s " " -w '%03d' 10
jot -s " " -w '%03d' 50
jot -s " " -w '%03d' 100

If you need to pad values up to a variable number with variable padding:
$values_count=514;
$padding_width=5;
for i in 0 `seq 1 $(($values_count - 1))`; do printf "%0*d\n" $padding_width $i; done;
This would print out 00000, 00001, ... 00513.
(I didn't find any of the current answers meeting my need)

Related

Replacing the value of specific field in a table-like string stored as bash variable

I am looking for a way to replace (with 0) a specific value (1043252782) in a "table-like" string stored as a bash variable. The output of echo "$var"looks like this:
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
7 Seek_Error_Rate 0x000f 090 060 045 Pre-fail Always - 1043252782
10 Spin_Retry_Count 0x0013 100 100 097 Pre-fail Always - 0
187 Reported_Uncorrect 0x0032 100 100 000 Old_age Always - 0
After the replacement echo "$var" should look like this:
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
7 Seek_Error_Rate 0x000f 090 060 045 Pre-fail Always - 0
10 Spin_Retry_Count 0x0013 100 100 097 Pre-fail Always - 0
187 Reported_Uncorrect 0x0032 100 100 000 Old_age Always - 0
Is there a way to do this without saving the content of $var to a file and directly manipulating it within the bash (shell script)?
Maby with awk? I can select the value in the 10th field of the second record with awk and pattern matching ("7 Seek_Error_Rate ....") like this:
echo "$var" | awk '/^ 7/{print $10}'
Maby there is some way doing it with awk (or other cli-tool) to replace it and store it back into $var? Also, the value changes over time, but the structure remains the same (some record at the 10th field).
You can change a specific string directly in the shell:
var=${var/1043252782/0}
To replace final number of second line, you could use awk or sed:
var=$(awk 'NR==2 { sub(/[0-9]+$/,0) }1' <<<"$var")
var=$(sed '2s/[0-9][0-9]*$/0/' <<<"$var")
If you don't know which line it will be, you can match a known string:
var=$(awk '/Seek_Error_Rate/{ sub(/[0-9]+$/,0) }1' <<<"$var")
var=$(sed '/Seek_Error_Rate/s/[0-9][0-9]*$/0/' <<<"$var")
You can use a here-string to feed the variable as input to awk.
Use sub() to perform a regular expression replacement.
var=$(awk '{sub(/1043252782$/, "0")}1' <<<"$var")
Using sed
$ var=$(sed '/1043252782$/s//0/' <<< "$var")
$ echo "$var"
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
7 Seek_Error_Rate 0x000f 090 060 045 Pre-fail Always - 0
10 Spin_Retry_Count 0x0013 100 100 097 Pre-fail Always - 0
187 Reported_Uncorrect 0x0032 100 100 000 Old_age Always - 0
if you don't wanna ruin formatting of tabs and spaces :
{m,g}wk NF=NF FS=' 1043252782$' OFS=' 0'
:
5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - 0
7 Seek_Error_Rate 0x000f 090 060 045 Pre-fail Always - 0
10 Spin_Retry_Count 0x0013 100 100 097 Pre-fail Always - 0
187 Reported_Uncorrect 0x0032 100 100 000 Old_age Always - 0
or doing the whole file in one single shot :
awk NF=NF FS=' 1043252782\n' OFS=' 0\n' RS='^$' ORS=
awk NF=NF FS=' 1043252782\n' OFS=' 0\n' RS= -- (This might work too but I'm not too well versed in any side effects for blank RS)

String to Integer conversions in shell script and back to String

I would like to do String to Integer conversion, operate that integer and back to string in shell.
I have
input_sub=000
while [ -d $input_dir ]
do
echo $input_sub
# HERE I would like to fist convert 000 to 0
# then add 1 to it 0-> 1
# then convert that 1 to 001
done
Don't mind much about the while condition.
I would like to do what is described in the comments.
How can I do this?
You can do what you need in POSIX shell, but you must protect against numbers with leading zeros being interpreted as octal numbers. To do what you want, you need a way to remove the leading zeros for your conversion to a number. While bash provides a simple built-in parameter expansion that will work, in POSIX shell, you are stuck using the old expr syntax or calling a utility like sed or grep.
To trim the leading zeros using expr, you must first know how many there are. The old POSIX shell expr provides two expressions that will work. The first called index can return the index of the first character in $input_sub that is not 0. Which gives you the index (1-based) where the first non-zero digit is found. The form you can use is:
## get index of first non-zero digit, POSIX compliant
nonzero=$(expr index "$input_sub" [123456789])
With the index of the first non-zero digit in $nonzero, you can use the substr expression to obtain the number without leading zeros (you know the max number of digits is 3, so obtain the substring from the index to 3), e.g.
num=$(expr substr "$input_sub" "$nonzero" 3) ## remove leading 0's
You need to be able to handle 000 as $inpu_sub, so go ahead and add a if .. then ... else ... fi to handle that case, e.g.
if [ "$nonzero" -eq 0 ]; then
num=0
else
num=$(expr substr "$input_sub" "$nonzero" 3) ## remove leading 0's
fi
Now you can simply add 1 to get your new number:
newnum=$((num + 1))
To convert the number back to a string of 3 characters representing the number with leading zeros replaced, just use printf with the "%03d" conversion specifier, e.g.
# then convert that 1 to 001
input_sub=$(printf "%03d" "$newnum")
Putting together a short example showing the progression that takes place, I have replaced your while loop with a loop that will loop 21 times from 0 to 20 to show the operation and I have added printf statements to show the numbers and conversion back to string. You simply restore your while and remove the extra printf statements for your use:
#!/bin/sh
input_sub=000
# while [ -d $input_dir ]
while [ "$input_sub" != "020" ] ## temporary loop 000 to 009
do
printf "input_sub: %s " "$input_sub"
# HERE I would like to fist convert 000 to 0
# then add 1 to it 0-> 1
## get index of first non-zero digit, POSIX compliant
nonzero=$(expr index "$input_sub" [123456789])
if [ "$nonzero" -eq 0 ]; then
num=0
else
num=$(expr substr "$input_sub" "$nonzero" 3) ## remove leading 0's
fi
newnum=$((num + 1))
# then convert that 1 to 001
input_sub=$(printf "%03d" "$newnum")
printf "%2d + 1 = %2d => input_sub: %s\n" "$num" "$newnum" "$input_sub"
done
Example Use/Output
Showing the conversions with the modified while loop, you would get:
$ sh str2int2str.sh
input_sub: 000 0 + 1 = 1 => input_sub: 001
input_sub: 001 1 + 1 = 2 => input_sub: 002
input_sub: 002 2 + 1 = 3 => input_sub: 003
input_sub: 003 3 + 1 = 4 => input_sub: 004
input_sub: 004 4 + 1 = 5 => input_sub: 005
input_sub: 005 5 + 1 = 6 => input_sub: 006
input_sub: 006 6 + 1 = 7 => input_sub: 007
input_sub: 007 7 + 1 = 8 => input_sub: 008
input_sub: 008 8 + 1 = 9 => input_sub: 009
input_sub: 009 9 + 1 = 10 => input_sub: 010
input_sub: 010 10 + 1 = 11 => input_sub: 011
input_sub: 011 11 + 1 = 12 => input_sub: 012
input_sub: 012 12 + 1 = 13 => input_sub: 013
input_sub: 013 13 + 1 = 14 => input_sub: 014
input_sub: 014 14 + 1 = 15 => input_sub: 015
input_sub: 015 15 + 1 = 16 => input_sub: 016
input_sub: 016 16 + 1 = 17 => input_sub: 017
input_sub: 017 17 + 1 = 18 => input_sub: 018
input_sub: 018 18 + 1 = 19 => input_sub: 019
input_sub: 019 19 + 1 = 20 => input_sub: 020
This has been done in POSIX shell given your tag [shell]. If you have bash available, you can shorten and make the script a bit more efficient by using bash built-ins instead of expr. That said, for 1000 directories max -- you won't notice much difference. Let me know if you have further questions.
Bash Solution Per-Request in Comment
If you do have bash available, then the [[ ... ]] expression provides the =~ operator which allows an extended REGEX match on the right hand side (e.g. [[ $var =~ REGEX ]]) The REGEX can contain capture groups (parts of the REGEX enclosed by (..)), that are used to fill the BASH_REMATCH array where ${BASH_REMATCH[0]} contains the total expression matched and ${BASH_REMATCH[1]} ... contain each captured part of the regex.
So using [[ ... =~ ... ]] with a capture on the number beginning with [123456789] will leave the wanted number in ${BASH_REMATCH[1]} allowing you to compute the new number using the builtin, e.g.
#!/bin/bash
input_sub=000
# while [ -d $input_dir ]
while [ "$input_sub" != "020" ] ## temporary loop 000 to 020
do
printf "input_sub: %s " "$input_sub"
# HERE I would like to fist convert 000 to 0
# then add 1 to it 0-> 1
## [[ .. =~ REGEX ]], captures between (...) in array BASH_REMATCH
if [[ $input_sub =~ ^0*([123456789]+[0123456789]*)$ ]]
then
num=${BASH_REMATCH[1]} ## use number if not all zeros
else
num=0 ## handle 000 case
fi
newnum=$((num + 1))
# then convert that 1 to 001
input_sub=$(printf "%03d" "$newnum")
printf "%2d + 1 = %2d => input_sub: %s\n" "$num" "$newnum" "$input_sub"
done
(same output)
Let me know if you have further questions.

Bash - Fill with 0 every number to make it 4 digits using sed

I have tried different ways of doing this. I need to transform this:
5 dias, 3 meses, 4 anos
67 decimales
naranjas 45, limones 56
66 + 44 = 100.
7777a 7b 88c 777d
Into this:
0005 dias, 0003 meses, 0004 anos
0067 decimales
naranjas 0045, limones 0056
0066 + 0044 = 0100.
7777a 0007b 0088c 0777d
But I cannot manage to add zeros to the last line with the code I have so far, which is the following:
sed -e 's/\b[0-9]\{3\}\b/0&/g; s/\b[0-9]\{2\}\b/00&/g; s/\b[0-9]\b/000&/g' numbers.txt >output.txt
What am I missing?
You could use perl
perl -pe 's/\d+/sprintf "%04d",$&/ge' test
or if you really want to use sed
sed -r ':1;s/([^0-9]|^)([0-9]{1,3})([^0-9]|$)/\10\2\3/;t1' file

While loop in bash getting duplicate result

$ cat grades.dat
santosh 65 65 65 65
john 85 92 78 94 88
andrea 89 90 75 90 86
jasper 84 88 80 92 84
santosh 99 99 99 99 99
Scripts:-
#!/usr/bin/bash
filename="$1"
while read line
do
a=`grep -w "santosh" $1 | awk '{print$1}' |wc -l`
echo "total is count of the file is $a";
done <"$filename"
O/p
total is count of the file is 2
total is count of the file is 2
total is count of the file is 2
total is count of the file is 2
total is count of the file is 2
Real O/P should be
total is count of the file is 2 like this right..please let me know,where i am missing in above scripts.
Whilst others have shown you better ways to solve your problem, the answer to your question is in the following line:
a=`grep -w "santosh" $1 | awk '{print$1}' |wc -l`
You are storing names in the variable "line" through the while loop, but it is never used. Instead your loop is always looking for "santosh" which does appear twice and because you run the same query for all 5 lines in the file being searched, you therefore get 5 lines of the exact same output.
You could alter your current script like so:
a=$(grep -w "$line" "$filename" | awk '{print$1}' | wc -l)
The above is not meant to be a solution as others have pointed out, but it does solve your issue.

How can I compare two 2D-array files with bash?

I have two 2D-array files to read with bash.
What I want to do is extract the elements inside both files.
These two files contain different rows x columns such as:
file1.txt (nx7)
NO DESC ID TYPE W S GRADE
1 AAA 20 AD 100 100 E2
2 BBB C0 U 200 200 D
3 CCC 9G R 135 135 U1
4 DDD 9H Z 246 246 T1
5 EEE 9J R 789 789 U1
.
.
.
file2.txt (mx3)
DESC W S
AAA 100 100
CCC 135 135
EEE 789 789
.
.
.
Here is what I want to do:
Extract the element in DESC column of file2.txt then find the corresponding element in file1.txt.
Extract the W,S elements in such row of file2.txt then find the corresponding W,S elements in such row of file1.txt.
If [W1==W2 && S1==S2]; then echo "${DESC[colindex]} ok"; else echo "${DESC[colindex]} NG"
How can I read this kind of file as a 2D array with bash or is there any convenient way to do that?
bash does not support 2D arrays. You can simulate them by generating 1D array variables like array1, array2, and so on.
Assuming DESC is a key (i.e. has no duplicate values) and does not contain any spaces:
#!/bin/bash
# read data from file1
idx=0
while read -a data$idx; do
let idx++
done <file1.txt
# process data from file2
while read desc w2 s2; do
for ((i=0; i<idx; i++)); do
v="data$i[1]"
[ "$desc" = "${!v}" ] && {
w1="data$i[4]"
s1="data$i[5]"
if [ "$w2" = "${!w1}" -a "$s2" = "${!s1}" ]; then
echo "$desc ok"
else
echo "$desc NG"
fi
break
}
done
done <file2.txt
For brevity, optimizations such as taking advantage of sort order are left out.
If the files actually contain the header NO DESC ID TYPE ... then use tail -n +2 to discard it before processing.
A more elegant solution is also possible, which avoids reading the entire file in memory. This should only be relevant for really large files though.
If the rows order is not needed be preserved (can be sorted), maybe this is enough:
join -2 2 -o 1.1,1.2,1.3,2.5,2.6 <(tail -n +2 file2.txt|sort) <(tail -n +2 file1.txt|sort) |\
sed 's/^\([^ ]*\) \([^ ]*\) \([^ ]*\) \2 \3/\1 OK/' |\
sed '/ OK$/!s/\([^ ]*\) .*/\1 NG/'
For file1.txt
NO DESC ID TYPE W S GRADE
1 AAA 20 AD 100 100 E2
2 BBB C0 U 200 200 D
3 CCC 9G R 135 135 U1
4 DDD 9H Z 246 246 T1
5 EEE 9J R 789 789 U1
and file2.txt
DESC W S
AAA 000 100
CCC 135 135
EEE 789 000
FCK xxx 135
produces:
AAA NG
CCC OK
EEE NG
Explanation:
skip the header line in both files - tail +2
sort both files
join the needed columns from both files into one table like, in the result will appears only the lines what has common DESC field
like next:
AAA 000 100 100 100
CCC 135 135 135 135
EEE 789 000 789 789
in the lines, which have the same values in 2-4 and 3-5 columns, substitute every but 1st column with OK
in the remainder lines substitute the columns with NG

Resources