Flip multiple strings vertically on bash - bash

I am trying to flip the contents of any sentence vertically. So each chars of any string will get printed vertically in same line. For Example:
Sample Text: This is an Example
Output expected: T i a E
h s n x
i a
s m
p
l
e
In following direction I am trying to achieve this but not able to yet.
echo "Input provided by user is $#"
for i in $(seq 1 $#); do
echo ${!i} | sed 's/./ &/g' | xargs |tr ' ' '\n'
done
Current output:
T
h
i
s
i
s
a
n
E
x
a
m
p
l
e
Also, This is also not helping
echo Print text vertically | fold -c -w1
T
h
i
s
i
s
a
n
E
x
a
m
p
l
e
More alternatives which did not worked :
#echo "Input provided by user is $#"
for i in $(seq 1 $#); do
content[i]=$(echo ${!i}|fold -c -w1)
#echo ${content[i]}
done
echo ${content[#]}

max variable holds the max length among all words. For your text it would be: length('Example') which is 7 (maximum out of lengths of all words)
Using an awk script file:
$ awk -f script.awk <<< "This is an Example"
TiaE
hsnx
i a
s m
p
l
e
And here is the script:
{
max=0
for(i=1;i<=NF;i++)
max=length($i)>max?length($i):max;
for(j=1;j<=max;j++)
{
for(i=1;i<=NF;i++)
{
temp=substr($i, j, 1);
printf temp==""?" ":temp
}
printf "\n"
}
}

#!/bin/bash
function abc(){
maxIteration=0;
for i in $(seq 1 $#); do
j=$(echo ${!i})
if [ $maxIteration -lt ${#j} ]
then
maxIteration=${#j};
fi
done
COUNTER=0;
while [ $COUNTER -lt $maxIteration ]; do
for i in $(seq 1 $#); do
j=$(echo ${!i})
if [ ${#j} -gt $COUNTER ]
then
echo ${j:$COUNTER:1} | tr '\n' ' ';
else
echo " " | tr '\n' ' ';
fi
done
echo -e "\n"
let COUNTER=COUNTER+1
done
}
abc $#| grep .

I had created some similar script before. A short but complete POC:
#!/bin/bash
count=0
max=0
#first determine the longest string so we can later pad shorter strings with spaces
for i in $(echo "$1" | xargs -d: -i echo {})
do
size=$(echo $i | wc -c)
if [[ $size > $max ]]
then
max=$size
fi
done
files=""
#then echo the strings vertically inside the tmp files
for i in $(echo "$1" | xargs -d: -i echo {})
do
res=$(echo $i | sed 's/./ &/g' | xargs |tr ' ' '\n' > /tmp/$count.out)
#and add spaces at the end
add_space=$((max-$(echo $i | wc -c)))
for space in $(seq 0 $add_space)
do
echo " " >> /tmp/$count.out
done
files=$files" $count.out"
count=$((count+1))
done
#and finally print them side by side
pr -t -J -m -w 70 -S" " $files
I create tmp files under /tmp, echo the string vertical and later use pr to print it out.
% ./s.sh "This is an Example"
T i a E
h s n x
i a
s m
p
l
e

Because Perl is fun:
perl -a script.pl <<< 'This is an Example'
T i a E
h s n x
i a
s m
p
l
e
And the script:
#F = map { [/./g] } #F;
while (grep #{$_}, #F) {
printf "%s ", shift #{$_} || ' ' for #F;
print "\n"
}
Alternative script:
perl -pe '$r.=$/while/\S/&&s/(\S)(\S*)|\B/$r.=($1||$").$";$2/ge}{$_=$r' \
<<< 'This is an Example'
T i a E
h s n x
i a
s m
p
l
e

Related

Code to count the number of sequential characters

For example, if the input is aabcca, the output needs to be a2b1c2a1 not a3b1c2
I originally wrote this -
echo "aabcca" > file.txt
a=0
b=0
c=0
while IFS= read -r -n1 char
do
[ "$char" == "a" ] && (( a++ ))
[ "$char" == "b" ] && (( b++ ))
[ "$char" == "c" ] && (( c++ ))
done < file.txt
echo "a${a}b${b}c${c}"
But this outputs a3b1c2. I want a2b1c2a1.
Using awk, you may do this:
awk '{
p=c=""
for (i=1; i<=length(); ++i) {
f=substr($0, i, 1)
if (p != "" && f != p) {
printf "%s", p c
c = 0
}
++c
p = f
}
print p c
}' file.txt
a2b1c2a1
How about:
#!/usr/bin/env bash
count=0
read -r -n1 prev_char < file.txt
while IFS= read -r -n1 char
do
if [ "$prev_char" != "$char" ]
then
printf "%c%d" "$prev_char" "$count"
count=0
fi
prev_char="$char"
count=$((count + 1))
done < file.txt
printf "\n"
Here's an one-liner way to do it:
tr '\n' ' ' < file.txt | fold -w1 | uniq -c | awk '$2!=""{printf "%s", $2 $1} END {printf "\n"}'
EDIT: Also if you want to get rid of punctuation characters, just add this to tr:
tr '\n[:punct:]' ' ' < file.txt | fold -w1 | uniq -c | awk '$2!=""{printf "%s", $2 $1} END {printf "\n"}'

how will I fix the echo when counting chars in a string

I have an issue with the echo in the for loop, as I want to count string chars especailly for "*" it but it prints all the files in current directory.
clearvar() {
int=0
str=0
uniqchar=0
}
countstring(){
for c in $(echo "${1}" | fold -w1); do
echo "$c"
if [[ $c == [0-9] ]];then
int=$(( $int + 1 ))
elif [[ $c == [a-Z] ]];then
str=$(( $str + 1 ))
else
uniqchar=$(( $uniqchar + 1 ))
fi
done
}
while [ $# -gt 0 ];do
echo "Argument input: $1"
read -p "Input: " string
rmws=$(echo $string | tr -d " ")
mashed=$rmws$1
countstring $mashed
echo -e "int: $int\nstr: $str\nuniquechar: $uniqchar\nWhole string: $mashed"
clearvar
shift
done
Example output:
Argument input: io1
Input: fj^*23
f
j
^
file1
file2
file3
2
3
i
o
1
int: 3
str: 4
uniquechar: 4
Whole string: fj^*2wio1
it interprets as echo * instead of echo "*".
so I expect it to not print out the file names.
rmws=$(echo $string | tr -d " ")
If string=* this just executes echo * and expands the *.
The same happens in:
countstring $mashed
Both these expansions are unquoted. Quote them in double quotes. As a rule of a thumb - always use double quotes.
Also the same happens in the for loop:
for c in $(echo "${1}" | fold -w1)
the expansion, as elsewhere, is unquoted, so * expands. You have to quote. That's why the for i in $(..) is considered bad style - because such bugs happen. You can't do for i in "$(...)" because then you would iterate over one element. To iterate over lines or elements in a stream use a while IFS= read -r loop. You can print every character on each separate line with ex. sed 's/./&\n/g' and iterate over lines, or use bash extension read -n1 to read one character.
while IFS= read -r -n1 c; do
..
done <<<"$1"
The <<<"$1" is a bash's "here string".
You don't need $ in arithmetic expansion. Just:
int=$(( int + 1 ))
str=$(( str + 1 ))
uniqchar=$(( uniqchar + 1 ))
or in bash you can even do:
(( int++ ))
# and so on
Your script could become:
clearvar() {
int=0
str=0
uniqchar=0
}
countstring(){
while IFS= read -r -n1 c; do
echo "$c"
if [[ $c == [0-9] ]];then
(( int++ ))
elif [[ $c == [a-Z] ]];then
(( str++ ))
else
(( uniqchar++ ))
fi
done <<<"$1"
}
while [ $# -gt 0 ]; do
echo "Argument input: $1"
read -p "Input: " string
rmws="$(echo "$string" | tr -d " ")"
mashed="$rmws$1"
countstring "$mashed"
echo "int: $int"
echo "str: $str"
echo "uniquechar: $uniqchar"
echo "Whole string: $mashed"
clearvar
shift
done
Notes:
echo has portability issues. Prefer to use printf instead.
I prefer while (($#)); do in place of while [ $# -eq 0 ]; do.
PS. I would use tr:
countstring() {
printf "%s" "$1" | tr -cd '[0-9]' | wc -c
printf "%s" "$1" | tr -cd '[a-zA-Z]' | wc -c
printf "%s" "$1" | tr -d '[0-9a-zA-Z]' | wc -c
}

extract a numeric substring and add value to it

I have a string like 1001.2001.3001.5001.6001 or 1001-2001-3001-5001-6001. How to extract the 4th string i.e., 5001, add a value like 121 to it and put it back in the same string. The output should be like 1001.2001.3001.5122.6001 or 1001-2001-3001-5122-6001. I have to achieve this in Linux bash scripting.
Try this
#!/bin/bash
str=$1
if [[ $(echo $str | grep '\.' | wc -l) == 1 ]]
then
str1=$(echo $str | cut -d '.' -f 1,2,3)
str2=$(echo $str | cut -d '.' -f 4 | awk {'print $1+121'})
str3=$(echo $str | cut -d '.' -f 5)
echo $str1.$str2.$str3
elif [[ $(echo $str | grep - | wc -l) == 1 ]]
then
str1=$(echo $str | cut -d '-' -f 1,2,3)
str2=$(echo $str | cut -d '-' -f 4 | awk {'print $1+121'})
str3=$(echo $str | cut -d '-' -f 5)
echo $str1-$str2-$str3
else
echo "do nothing"
fi
Pass a string as parameter
No pipes, no forks, no cutting, no awking, just plain POSIX shell:
$ s=1001.2001.3001.5001.6001
$ oldIFS=$IFS
$ IFS=.-
$ set -- $s
$ case $s in
> (*.*) echo "$1.$2.$3.$(($4 + 121)).$5";;
> (*-*) echo "$1-$2-$3-$(($4 + 121))-$5";;
> esac
1001.2001.3001.5122.6001
$ IFS=$oldIFS
One liner
value=121 ; str='1001.2001.3001.5001.6001' ; token="$(echo "$str" | cut -f 4 -d '.')" ; newtoken=$(( $token + $value )) ; newstr="$(echo "$str" | sed -e "s/$token/$newtoken/g" | tr '.' '-')" ; echo "$newstr"
Breakdown:
value=121 # <- Increment
str='1001.2001.3001.5001.6001' # <- Initial String
token="$(echo "$str" | cut -f 4 -d '.')" # <- Extract the 4th field with . sep
newtoken=$(( $token + $value )) # <- Add value and save to $newtoken
newstr="$(echo "$str" \
| sed -e "s/$token/$newtoken/g" \
| tr '.' '-')" # <- Replace 4th field with $newtoken
# and translate "." to "-"
echo "$newstr" # <- Echo new string
Works in:
Bash
sh
FreeBSD
Busybox
Using out of the box tools
If the field separator can either be . or -, then do something like
echo "1001.2001.3001.5001.6001" | awk 'BEGIN{FS="[.-]";OFS="-"}{$4+=121}1'
1001-2001-3001-5122-6001
However, if you need to match the regex FS or field separator with OFS then you need to have gawk installed
echo "1001.2001.3001.5001.6001" |
gawk 'BEGIN{FS="[.-]"}{split($0,a,FS,seps)}{$4+=121;OFS=seps[1]}1'
1001.2001.3001.5122.6001
Though resetting the argument list with the values is probably the preferred way, or by setting IFS to the delimiter and reading the values into an array and adding the desired value to the array index at issue, you can also do it with a simple loop to look for the delimiters and continually skipping characters until the desired segment is found (4 in you case -- when the delimiter count is 3). Then simply appending the digit at each array index until your next delimiter is found will give you the base value. Simply adding your desired 121 to the completed number completes the script, e.g.
#!/bin/bash
str=${1:-"1001.2001.3001.5001.6001"} ## string
ele=${2:-4} ## element to add value to [1, 2, 3, ...]
add=${3:-121} ## value to add to element
cnt=0 ## flag to track delimiters found
num=
## for each character in str
for ((i = 0; i < ${#str}; i++))
do
if [ "${str:$i:1}" = '.' -o "${str:$i:1}" = '-' ] ## is it '.' or '-'
then
(( cnt++ )) ## increment count
(( cnt == ele )) && break ## if equal to ele, break
## check each char is a valid digit 0-9
elif [ "0" -le "${str:$i:1}" -a "${str:$4i:1}" -le "9" ]
then
(( cnt == (ele - 1) )) || continue ## it not one of interest, continue
num="$num${str:$i:1}" ## append digit to num
fi
done
((num += add)) ## add the amount to num
printf "num: %d\n" $num ## print results
Example Use/Output
$ bash parsenum.sh
num: 5122
$ bash parsenum.sh "1001.2001.3001.5001.6001" 2
num: 2122
$ bash parsenum.sh "1001.2001.3001.5001.6001" 2 221
num: 2222
Look things over and let me know if you have any questions.

How to convert decimal number to Base58 in Bash

myNumber=$(date +%s) # big number in decimal
myNumberInB58=$(toBase58 $myNumber)
toBase58() {
# <your answer here>
}
What is the most elegant and/or concise way to encode an integer in Base58?
The bitcoin-bash-tools provide the functions {en,de}codeBase58:
decodeBase58() {
echo -n "$1" | sed -e's/^\(1*\).*/\1/' -e's/1/00/g' | tr -d '\n'
dc -e "$dcr 16o0$(sed 's/./ 58*l&+/g' <<<$1)p" |
while read n; do echo -n ${n/\\/}; done
}
encodeBase58() {
echo -n "$1" | sed -e's/^\(\(00\)*\).*/\1/' -e's/00/1/g' | tr -d '\n'
dc -e "16i ${1^^} [3A ~r d0<x]dsxx +f" |
while read -r n; do echo -n "${base58[n]}"; done
}
Those work with the fields dcr and base58 defined directly above in the file.
Would this do?
a=( {1..9} {A..H} {J..N} {P..Z} {a..k} {m..z} )
toBase58() {
# TODO: check that $1 is a valid number
local nb=$1 b58= fiftyeight=${#a[#]}
while ((nb)); do
b58=${a[nb%fiftyeight]}$b58
((nb/=fiftyeight))
done
printf '%s\n' "$b58"
}
Here's another version:
# Order-Preserving Base58 (OPB58).
# Also supports negative numbers.
int2b58() {
# Omit IOlo
local n="$1" i BASE58=$(echo {0..9} {A..H} {J..N} {P..Z} {a..k} {m..n} {p..z} | tr -d ' ')
((n < 0 )) && printf -- '-' && n=$((-n))
for i in $(echo "obase=58; $n" | bc); do
printf ${BASE58:$(( 10#$i )):1}
done; echo
}

count words in a file without using wc

Working in a shell script here, trying to count the number of words/characters/lines in a file without using the wc command. I can get the file broken into lines and count those easy enough, but I'm struggling here to get the words and the characters.
#define word_count function
count_stuff(){
c=0
w=0
l=0
local f="$1"
while read Line
do
l=`expr $line + 1`
# now that I have a line I want to break it into words and characters???
done < "$f"
echo "Number characters: $chars"
echo "Number words: $words"
echo "Number lines: $line"
}
As for characters, try this (adjust echo "test" to where you get your output from):
expr `echo "test" | sed "s/./ + 1/g;s/^/0/"`
As for lines, try this:
expr `echo -e "test\ntest\ntest" | sed "s/^.*$/./" | tr -d "\n" | sed "s/./ + 1/g;s/^/0/"`
===
As for your code, you want something like this to count words (if you want to go at it completely raw):
while read line ; do
set $line ;
while true ; do
[ -z $1 ] && break
l=`expr $l + 1`
shift ;
done ;
done
You can do this with the following Bash shell script:
count=0
for var in `cat $1`
do
count=`echo $count+1 | bc`
done
echo $count

Resources