12345678 = 123 45 67 8 in bash - bash

I have a script that takes in one big 17 digit number as input from the command line. I want to separate it into 5 different numbers, each with different no. of digits. Like so:
Input: 23063080434560228
Output of the program:
Number1: 23063
Number2: 08
Number3: 04
Number4: 3456
Number5: 0228
Now the output of the program (in terms of digits per number) is fixed, i.e, Number2 will always have 2 digits and so on. Given this sort of a scheme of the output, I am not sure if division is even an option. Is there some bash command/utility that I can use? I have looked it over the net and not come across much.
Thanks,
Sriram.

You can use Substring Extraction:
${string:position:length}
Extracts length characters of substring from string starting at position.
For example:
INPUT=23063080434560228
num1=${INPUT:0:5}
num2=${INPUT:5:2}
num3=${INPUT:7:2}
num4=${INPUT:9:4}
num5=${INPUT:13:4}

You can put this directly into an array in one shot in Bash 3.2 or greater.
string=23063080434560228
pattern='(.{5})(.{2})(.{2})(.{4})(.{4})'
[[ $string =~ $pattern ]] && substrings=(${BASH_REMATCH[#]:1})
for substring in "${substrings[#]}"; do echo "$substring"; done

Related

BASH count with two digit even its a one digit task

i'am currently trying to count +1 to a variable which could contain either
02
15
i use the following line to count one up, but the output seems not to be two digit. So if 02 is the $version and count one up, it will give the output "3" and not "03". Sure if its already two digit, it works.
$((echo $version) + 01))
how can I get bash to count with two digits and output it correctly?
Use printf to format a number. Also, note that 08 fails in numeric expressions, as numbers starting with 0 are interpreted as octal.
#!/bin/bash
shopt -s extglob
for n in 02 08 15 001; do
i=${n##+(0)} # Remove leading zeros. Needs the extglob.
length=${#n}
printf "%0${length}d\\n" $((i + 1))
done

Script to create a four character string based on known numerical relationships

Consider a three line input file containing four unique numbers (1,2,3,4) such that each line represents the position of one number relative to another number.
So for example in the following input set, 4 is next to 2, 2 is next to 3, and 1 is next to 4.
42
23
14
So given that how would a script assemble all four numbers in such a way that it maintains each numbers known relationship?
In other words there are two answers 1423 or 3241 but how to arrive at that programmatically?
Not very sensible or efficient, but fun (for me, at least) :-)
This will echo all the permutations using GNU Parallel:
parallel echo {1}{2}{3}{4} ::: {1..4} ::: {1..4} ::: {1..4} ::: {1..4}
And add some grepping on the end:
parallel echo {1}{2}{3}{4} ::: {1..4} ::: {1..4} ::: {1..4} ::: {1..4} | grep -E "42|24" | grep -E "23|32" | grep -E "14|41"
Output
1423
3241
Brute forcing the luck:
for (( ; ; ))
do
res=($(echo "42
23
14" | shuf))
if ((${res[0]}%10 == ${res[1]}/10 && ${res[1]}%10 == ${res[2]}/10))
then
echo "success: ${res[#]}"
break
fi
echo "fail: ${res[#]}"
done
fail: 42 14 23
fail: 42 23 14
fail: 42 14 23
success: 14 42 23
For 3 numbers, this approach is acceptable.
Shuf shuffles the input lines and fills the array res with the numbers.
Then we take to following numbers and test, if the last digit of the first matches the first digit of the next, and for the 2nd and 3rd number accordingly.
If so, we break with a success message. For debugging, a failure message is better than a silent endless loop.
For longer chains of numbers, a systematic permutation might be better to test and a function to check two following numbers, which can be called by index or better a loop would be suitable.

Zero padding numbers in a Bash loop

I'm trying to make a list with a simple bash looping
I want this:
000000
000001
000002
They give me this:
0
1
2
My shell code:
countBEG="000000"
countEND="999999"
while [ $countBEG != $countEND ]
do
echo "$countBEG"
countBEG=$[$countBEG +1]
done
Change your echo to use printf, where you can specify format for left padding.
printf "%06d\n" "$countBEG"
This sets 6 as fixed length of the output, using zeros to fill empty spaces.
You're looking for:
seq -w "$countBEG" "$countEND"
The -w option does the padding.
The following command will produce the desired output (no need for the loop) :
printf '%06d\n' {1..999999}
Explanation :
{1..999999} is expanded by bash to the sequence of 1 to 999999
the format string '%06d\n' tells printf to display the number it is given as argument padded to 6 digits and followed by a linefeed
printf repeats this output if it is given more arguments than is defined in its format specification

How can I find the missing integers in a unique and sequential list (one per line) in a unix terminal?

Suppose I have a file as follows (a sorted, unique list of integers, one per line):
1
3
4
5
8
9
10
I would like the following output (i.e. the missing integers in the list):
2
6
7
How can I accomplish this within a bash terminal (using awk or a similar solution, preferably a one-liner)?
Using awk you can do this:
awk '{for(i=p+1; i<$1; i++) print i} {p=$1}' file
2
6
7
Explanation:
{p = $1}: Variable p contains value from previous record
{for ...}: We loop from p+1 to the current row's value (excluding current value) and print each value which is basically the missing values
Using seq and grep:
seq $(head -n1 file) $(tail -n1 file) | grep -vwFf file -
seq creates the full sequence, grep removes the lines that exists in the file from it.
perl -nE 'say for $a+1 .. $_-1; $a=$_'
Calling no external program (if filein contains the list of numbers):
#!/bin/bash
i=0
while read num; do
while (( ++i<num )); do
echo $i
done
done <filein
To adapt choroba's clever answer for my own use case, I needed my sequence to deal with zero-padded numbers.
The -w switch to seq is the magic here - it automatically pads the first number with the necessary number of zeroes to keep it aligned with the second number:
-w, --equal-width equalize width by padding with leading zeroes
My integers go from 0 to 9999, so I used the following:
seq -w 0 9999 | grep -vwFf "file.txt"
...which finds the missing integers in a sequence from 0000 to 9999. Or to put it back into the more universal solution in choroba's answer:
seq -w $(head -n1 "file.txt") $(tail -n1 "file.txt") | grep -vwFf "file.txt"
I didn't personally find the - in his answer was necessary, but there may be usecases which make it so.
Using Raku (formerly known as Perl_6)
raku -e 'my #a = lines.map: *.Int; say #a.Set (^) #a.minmax.Set;'
Sample Input:
1
3
4
5
8
9
10
Sample Output:
Set(2 6 7)
I'm sure there's a Raku solution similar to #JJoao's clever Perl5 answer, but in thinking about this problem my mind naturally turned to Set operations.
The code above reads lines into the #a array, mapping each line so that elements in the #a array are Ints, not strings. In the second statement, #a.Set converts the array to a Set on the left-hand side of the (^) operator. Also in the second statement, #a.minmax.Set converts the array to a second Set, on the right-hand side of the (^) operator, but this time because the minmax operator is used, all Int elements from the min to max are included. Finally, the (^) symbol is the symmetric set-difference (infix) operator, which finds the difference.
To get an unordered whitespace-separated list of missing integers, replace the above say with put. To get a sequentially-ordered list of missing integers, add the explicit sort below:
~$ raku -e 'my #a = lines.map: *.Int; .put for (#a.Set (^) #a.minmax.Set).sort.map: *.key;' file
2
6
7
The advantage of all Raku code above is that finding "missing integers" doesn't require a "sequential list" as input, nor is the input required to be unique. So hopefully this code will be useful for a wide variety of problems in addition to the explicit problem stated in the Question.
OTOH, Raku is a Perl-family language, so TMTOWTDI. Below, a #a.minmax array is created, and grepped so that none of the elements of #a are returned (none junction):
~$ raku -e 'my #a = lines.map: *.Int; .put for #a.minmax.grep: none #a;' file
2
6
7
https://docs.raku.org/language/setbagmix
https://docs.raku.org/type/Junction
https://raku.org

How to work with large Hexadecimal values without converting to a string?

In bash I am SNMP'ing my printer to get some information, the snmp response looks like this:
080118000001FFFB000000020000FFFFFFFD0007FFFEFFFAFFFE0011FFFEFFD507FB000FFF8E001C0006FFF9FFF2FFFE000100040005000600030002000100000007FFFDFFFEFFFE00020008FFFFFFFF0000000100000000FFFC0004FFFDFFFE00010002
Based on the development documentation it states the first four byte-long elements in the header are to be
6th byte which is 18 can be interpreted in HEX mode which is pretty straight forward. For example, the number of logged on users which in the 6th byte is 18 HEX do it is 24 in decimal. The rest of the data is defined in two byte increments containing the real and imaginary
values and should be interpreted according to 2’s complement over the entire two bytes or 4 nibbles.
When converting these to 10 based numbers, do I have to convert it to a string and perform sub string functions or is there a function in bash that will allow me to convert it based on the byte value. Meaning I just want to convert the 17th byte?
In bash, you can treat any substring of the value as a hexadecimal number and output its decimal representation with
$ value=080118000001FFFB000000020000FFFFFFFD0007FFFEFFFAFFFE0011FFFEFFD507FB000FFF8E001C0006FFF9FFF2FFFE000100040005000600030002000100000007FFFDFFFEFFFE00020008FFFFFFFF0000000100000000FFFC0004FFFDFFFE00010002
$ echo $(( 16#${value:4:2} ))
24
Simply prefix the desired substring (here, two bytes starting at offset 4 from the beginning of the string) with 16# and evaluate it inside an arithmetic expression ($(( ... ))).
Using bash
$ s='0801180000...'
$ b=0x${s:4:2}
$ echo $((b))
24
To select the substring 18 from the string s, we use bash's substring expansion: ${s:4:2}. This is then combined with arithmetic expansion, $((...)) to convert the hexadecimal number to decimal.
Using awk
$ echo '0801180000...' | awk '{print strtonum("0x" substr($1,5,2));}'
24
How it works
substr($1,5,2)
This returns a 2-character substring from starting with the 5th character. For your input, this returns the 18.
Because awk uses and index origin of 1, rather than 0, the substring 18 starts at position 1 in awk.
"0x" substr($1,5,2)
Since, by convention, hexadecimal strings start with 0x, this add the string 0x to the substring we asked for to produce the string 0x18
strtonum("0x" substr($1,5,2))
This converts the string 0x18 from hexadecimal to decimal.

Resources