Convert from base6 to base64 in Bash - bash

I don't know much programming and have tried to find an answer, but have trouble finding anything even related to base6. I am trying to write a bash script to take a string of dice rolls and convert it to a base64 password.
I've tried
echo 13545142010250324013240412300102 |base64
but I get weird outputs such as
MTM1NDUxNDIwMTAyNTAzMjQwMTMyNDA0MTIzMDAxMDIK
or
MTM0MTIzNDM1MzQwMTIwNDMxMjQxCg==
which is not uniformly distributed.
What am I missing?

The base64 command expects binary input. Your base-6 string is not uniformly distributed on the set of characters that base64 accepts, therefore the output will also follow a severely restricted distribution.

One possibility: convert your number in radix 64 using bc and then convert each "digit" of this number into a character:
#!/bin/bash
pool=( {A..Z} {a..z} {0..9} + / )
shopt -s extglob
die() { (($#)) && printf >&2 '%s\n' "$#"; exit 1; }
[[ $1 = +([012345]) ]] || die "bad argument"
for i in $(BC_LINE_LENGTH=0 bc <<< "obase=${#pool[#]}; ibase=6; $1"); do
printf '%s' "${pool[10#$i]}"
done
printf '\n'
I called this script banana, I chmod +x banana and then:
$ ./banana 13545142010250324013240412300102
HSr/X8Hn0oUGFG
You can safely change the elements of pool: modify, add or remove elements in there, the new radix is automatically taken into account, since we used obase=${#pool[#]}, as long as there are at least 17 elements in pool.
Note. I'm using BC_LINE_LENGTH=0 to disable bc's output multi-line feature. If not available on your system, you'll have to handle trailing backslashes. The adaptation is straightforward and left as an exercise to the reader.

Related

How to pad a value with zeroes based on a match in a string and the length of the following string?

I have some problems adapting the answers from previous questions, so I hope it is ok to write for a specific solution.
I have a file with RNA-reads in the fasta format, however the end of the readname has been messed up, so I need to correct it.
It is a simple task of padding zeroes into the middle of a string, however I cannot get it to work as I also need to identify the length and the position of the problem.
My read file header looks like this:
#V350037327L1C001R0010000023/1_U1
and I need to search for the "/1_U" and then left pad zeroes to the rest of the line up to a total length of 6.
It will look like this:
#V350037327L1C001R0010000023/1_U000001
The final length should be six following "/1_U".
eg: input:
#V350037327L1C001R0010000055/1_U300 = /1_U000300
#V350037327L1C001R0010000122/1_U45000 = /1_U045000
I have tried with awk, however I cannot get it to check the initial length and hence not pad the correct number of zeroes.
Thank you in advance and thank you for your neverending support in this forum
Try this:
#! /bin/bash
files=('#V350037327L1C001R0010000023/1_U1'
'#V350037327L1C001R0010000055/1_U300'
'#V350037327L1C001R0010000122/1_U45000')
for file in "${files[#]}"; do
if [[ $file =~ ^(.*U)([0-9]+)$ ]]; then
printf '%s%06d\n' "${BASH_REMATCH[#]:1}"
fi
done
Update: This reads the files from stdin.
#! /bin/bash
while read -r file; do
if [[ $file =~ ^(.*U)([0-9]+)$ ]]; then
printf '%s%06d\n' "${BASH_REMATCH[#]:1}"
fi
done
Update 2: You should really learn the basics of shell programming before you start programming the shell. Typical basics are conditional constructs.
#! /bin/bash
while read -f file; do
if [[ $file =~ ^(.*U)([0-9]+)$ ]]; then
printf '%s%06d\n' "${BASH_REMATCH[#]:1}"
else
printf '%s\n' "$file"
fi
done

Bash generate random numbers from pool of numbers

I want to generate a random number from given list
For example if I give the numbers
1,22,33,400,400,23,12,53 etc.
I want to select a random number from the given numbers.
Couldn't find an exact duplicate of this. So here goes my attempt, exactly what 123 mentions in comments. The solution is portable across shell variants and does not make use of any shell binaries to simplify performance.
You can run the below commands directly on the console.
# Read the elements into bash array, with IFS being the de-limiter for input
IFS="," read -ra randomNos <<< "1,22,33,400,400,23,12,53"
# Print the random numbers using the '$RANDOM' variable built-in modulo with
# array length.
printf "%s\n" "${randomNos[ $RANDOM % ${#randomNos[#]}]}"
As per the comments below, if you want to ignore a certain list of numbers from a range to select; do the approach as below
#!/bin/bash
# Initilzing the ignore list with the numbers you have mentioned
declare -A ignoreList='([21]="1" [25]="1" [53]="1" [80]="1" [143]="1" [587]="1" [990]="1" [993]="1")'
# Generating the random number
randomNumber="$(($RANDOM % 1023))"
# Printing the number if it is not in the ignore list
[[ ! -n "${ignoreList["$randomNumber"]}" ]] && printf "%s\n" "$randomNumber"
You can save it in a bash variable like
randomPortNumber=$([[ ! -n "${ignoreList["$randomNumber"]}" ]] && printf "%s\n" "$randomNumber")
Remember associative-arrays need bash version ≥4 to work.

Extracting a string between last two slashes in Bash

I know this can be easily done using regex like I answered on https://stackoverflow.com/a/33379831/3962126, however I need to do this in bash.
So the closest question on Stackoverflow I found is this one bash: extracting last two dirs for a pathname, however the difference is that if
DIRNAME = /a/b/c/d/e
then I need to extract
d
This may be relatively long, but it's also much faster to execute than most preceding answers (other than the zsh-only one and that by j.a.), since it uses only string manipulations built into bash and uses no subshell expansions:
string='/a/b/c/d/e' # initial data
dir=${string%/*} # trim everything past the last /
dir=${dir##*/} # ...then remove everything before the last / remaining
printf '%s\n' "$dir" # demonstrate output
printf is used in the above because echo doesn't work reliably for all values (think about what it would do on a GNU system with /a/b/c/-n/e).
Here a pure bash solution:
[[ $DIRNAME =~ /([^/]+)/[^/]*$ ]] && printf '%s\n' "${BASH_REMATCH[1]}"
Compared to some of the other answers:
It matches the string between the last two slashes. So, for example, it doesn't match d if DIRNAME=d/e.
It's shorter and fast (just uses built-ins and doesn't create subprocesses).
Support any character between last two slashes (see Charles Duffy's answer for more on this).
Also notice that is not the way to assign a variable in bash:
DIRNAME = /a/b/c/d/e
^ ^
Those spaces are wrong, so remove them:
DIRNAME=/a/b/c/d/e
Using awk:
echo "/a/b/c/d/e" | awk -F / '{ print $(NF-1) }' # d
Edit: This does not work when the path contains newlines, and still gives output when there are less than two slashes, see comments below.
Using sed
if you want to get the fourth element
DIRNAME="/a/b/c/d/e"
echo "$DIRNAME" | sed -r 's_^(/[^/]*){3}/([^/]*)/.*$_\2_g'
if you want to get the before last element
DIRNAME="/a/b/c/d/e"
echo "$DIRNAME" | sed -r 's_^.*/([^/]*)/[^/]*$_\1_g'
OMG, maybe this was obvious, but not to me initially. I got the right result with:
dir=$(basename -- "$(dirname -- "$str")")
echo "$dir"
Using zsh parameter substitution is pretty cool too
echo ${${DIRNAME%/*}##*/}
I think it's faster than the double $() as well, because it won't need any subprocesses.
Basically it slices off the right side first, and then all the remaining left side second.

For loop with an argument based range

I want to run certain actions on a group of lexicographically named files (01-09 before 10). I have to use a rather old version of FreeBSD (7.3), so I can't use yummies like echo {01..30} or seq -w 1 30.
The only working solution I found is printf "%02d " {1..30}. However, I can't figure out why can't I use $1 and $2 instead of 1 and 30. When I run my script (bash ~/myscript.sh 1 30) printf says {1..30}: invalid number
AFAIK, variables in bash are typeless, so how can't printf accept an integer argument as an integer?
Bash supports C-style for loops:
s=1
e=30
for i in ((i=s; i<e; i++)); do printf "%02d " "$i"; done
The syntax you attempted doesn't work because brace expansion happens before parameter expansion, so when the shell tries to expand {$1..$2}, it's still literally {$1..$2}, not {1..30}.
The answer given by #Kent works because eval goes back to the beginning of the parsing process. I tend to suggest avoiding making habitual use of it, as eval can introduce hard-to-recognize bugs -- if your command were whitelisted to be run by sudo and $1 were, say, '$(rm -rf /; echo 1)', the C-style-for-loop example would safely fail, and the eval example... not so much.
Granted, 95% of the scripts you write may not be accessible to folks executing privilege escalation attacks, but the remaining 5% can really ruin one's day; following good practices 100% of the time avoids being in sloppy habits.
Thus, if one really wants to pass a range of numbers to a single command, the safe thing is to collect them in an array:
a=( )
for i in ((i=s; i<e; i++)); do a+=( "$i" ); done
printf "%02d " "${a[#]}"
I guess you are looking for this trick:
#!/bin/bash
s=1
e=30
printf "%02d " $(eval echo {$s..$e})
Ok, I finally got it!
#!/bin/bash
#BSD-only iteration method
#for day in `jot $1 $2`
for ((day=$1; day<$2; day++))
do
echo $(printf %02d $day)
done
I initially wanted to use the cycle iterator as a "day" in file names, but now I see that in my exact case it's easier to iterate through normal numbers (1,2,3 etc.) and process them into lexicographical ones inside the loop. While using jot, remember that $1 is the numbers amount, and the $2 is the starting point.

converting number to bitfield string in bash

What might be the most concise way in bash to convert a number into a bitfield character string like 1101?
In effect I am trying to do the opposite of
echo $[2#1101]
Why: I need to send a parameter to a program that takes bitfields in the form of a full string like "0011010110" but often only need to enable one or few bits as in:
SUPPRESSbits=$[1<<16] runscript.sh # OR
SUPPRESSbits=$[1<<3 + 1<<9] runscript.sh # much more readable when I know what bits 3 and 9 toggle in the program
Then runscript.sh then sees in its env a SUPPRESSbits=65536 rather than SUPPRESSbits="1000000000000000" and ends in parse error.
The easy way:
$ dc <<<2o123p
1111011
$ bc <<<'obase=2; 123'
1111011
I doubt about bash but you always can use perl:
a=123; b=$(perl -e 'printf "%b", "'$a'"'); echo $b
1111011

Resources