At the very beginning of my Bash script I'm passing a set of characters like this:
#!/bin/bash
set0="0 1 2 3 4 5 6 7 8 9"
set1="° § + \" ç % & / ( ) = ? ' ^ ! £ $ - _ ; , : . *"
The script combines every single character with the others in order to get all possible combination with length of n.
As you can see in set1 above I have to escape the quote " character for example. My problem now is, that I have to escape the asterisk * as well. But I couldn't figure out how.
This didn't work so far:
#!/bin/bash
set1="° § + \" ç % & / ( ) = ? ' ^ ! £ $ - _ ; , : . \*"
It echoes \*. If I don't escape the asterisk * with a backslash \ the script echoes all the files in current directory all time.
Do you know how I can escape the asterisk * successfully?
This is the whole script:
8 chars0="a b c d e f g h i j k l m n o p q r s t u v w x y z"
9 chars1="A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
10 chars2="0 1 2 3 4 5 6 7 8 9"
11 chars3="° § + \" ç % & / ( ) = ? ' ^ ! £ $ - _ ; , : . *"
12
13 function increment {
14 INPUT=$1
15 [[ ${#INPUT} -ge $minlen ]] && echo $1
16 if [[ ${#INPUT} -lt $maxlen ]]; then
17 for c in $chars; do
18 increment $1$c
19 done
20 fi
21 }
22
23 function showUsage {
24 echo "$0 MAXLEN [MINLEN] [a [A [n [p]]]]"
25 echo
26 echo " MAXLEN integer Maximum lenght, or specific lengt if MINLEN is omitted"
27 echo " MINLEN integer Minimum lenght, equals MAXLEN if omitted"
28 echo ""
29 echo " characters:"
30 echo " a lower case a-z"
31 echo " A Uppercase A-Z"
32 echo " n numeric 0-9"
33 echo " p punctuation . ?"
34 }
35
36
37
38
39 ## argument handler
40 [[ "$1" = "" ]] && showUsage && exit
41 maxlen=$1
42 [[ $2 -eq 0 ]] && minlen=$maxlen || minlen=$2
43
44
45 for arg in "$#"; do
46 case $arg in
47 a) chars="$chars $chars0" ;;
48 A) chars="$chars $chars1" ;;
49 n) chars="$chars $chars2" ;;
50 p) chars="$chars $chars3" ;;
51 esac;
52 done
53
54 #fallback
55 [[ "$chars" = "" ]] && chars="$chars0"
56
57
58
59 ## kickof
60
61 for i in $chars; do
62 increment $i
63 done
Thank you for all your suggestions. The one that helped me fixing this was
set -f
It's a quick fix of course. But it works well for me.
The best way to do this is maybe to use:
set -o noglob
set="* + ?"
for i in $set; do echo $i; done
*
+
?
The problem when you use the variable. noglob will prevent all of the file glob characters from expanding.
You can't safely loop on a list of words in a string like this, without having nasty globbing effects. In your case, it boils down to this:
a=*
for i in $a; do
echo "$a"
done
You'll see that the files in your current directory are printed, because the (unquoted! horror!) $a in the for statement expands to * which will glob to the list of files in current dir.
You must fix this by using another design. You're lucky, Bash supports arrays very well, so redesign your code to use arrays. For example:
chars=( ° § + \" ç % \& / '(' ')' = \? \' ^ ! £ $ - _ \; , : . \* )
(this defines the array chars) and to loop through that:
for i in "${chars[#]}"; do
echo "$i"
done
(observe the quotes).
Below is a rewriting of your code to show you how to use arrays (and I've fixed/improved some other minor stuff too):
#!/bin/bash
chars_alpha=( {a..z} )
chars_ALPHA=( {A..Z} )
chars_digit=( {0..9} )
chars_punct=( '°' '§' '+' '"' 'ç' '%' '&' '/' '(' ')' '=' '?' "'" '^' '!' '£' '$' '-' '_' ';' ',' ':' '.' '*' )
increment() {
local input=$1
(( ${#input} >= minlen )) && printf '%s\n' "$input"
if (( ${#input} < maxlen )); then
for c in "${chars[#]}"; do
increment "$input$c"
done
fi
}
showUsage() {
cat <<EOF
$0 MAXLEN [MINLEN] [a] [A] [n] [p]
MAXLEN integer Maximum lenght, or specific lengt if MINLEN is omitted
MINLEN integer Minimum lenght, equals MAXLEN if omitted
characters:
a lower case a-z
A Uppercase A-Z
n numeric 0-9
p punctuation . ? etc.
EOF
}
## argument handler
(($#)) || { showUsage; exit 1; }
[[ $1 = +([[:digit:]]) ]] || { showUsage; exit 1; }
((maxlen=10#$1))
shift
# Check that maxlen is >0 or exit gracefully
((maxlen>0)) || exit 0
if [[ $1 = +([[:digit:]]) ]]; then
((minlen=10#$1))
shift
else
((minlen=maxlen))
fi
# Check that minlen<=maxlen otherwise swap them
if ((minlen>maxlen)); then
((temp=minlen,minlen=maxlen,maxlen=temp))
fi
chars=()
while (($#)); do
case $1 in
(a) chars+=( "${chars_alpha[#]}" ); chars_alpha=() ;;
(A) chars+=( "${chars_ALPHA[#]}" ); chars_ALPHA=() ;;
(n) chars+=( "${chars_digit[#]}" ); chars_digit=() ;;
(p) chars+=( "${chars_punct[#]}" ); chars_punct=() ;;
(*) printf >&2 'Ignored arguments %s\n' "$1" ;;
esac
shift
done
#fallback
(( ${#chars[#]} )) || chars=( "${chars_alpha[#]}" )
#kick off!
increment
One of the fixes includes using printf instead of echo. With echo, your code is broken, since the strings:
-e
-n
-E
would never be printed! see help echo to understand why. Try it yourself too:
echo -e
echo -n
echo -E
don't output anything!
Use single quotes to wrap your string instead of double quotes. You won't have to have to escape any characters beside the single quote.
Related
How can I print 55 as hexadecimal with echo from decimal number?
#equivalent to:
printf "%x" 55
#like this:
dec=55
hex=$(([##16]dec))
echo $[0xAA]
I searched a lot but did not find any echo snippets for the calculation from decimal to hexadecimal.
Try this:
#!/bin/sh
OUTPUT=`echo $1 |dc -e "16o?p"`
echo $OUTPUT
and i have:
user#raspberrypi:~ $ ./convert.sh 999
3E7
How can I print 55 as hexadecimal with echo from decimal number?
It is not possible, echo as the name implies, echos what you give to it, it does no conversion.
like this:
Yes, you can write a utility to convert a decimal number into hexadecimal number in shell. There is no such builtin conversion.
Converting a number to hex, or any other base, is relatively easy. For example in Bash:
hex() {
local out="" dict=($(seq 0 9) a b c e d f)
while (($1)); do
out=${dict[$1 % 16]}$out
set -- $(($1 / 16))
done
echo $out
}
hex 55
Conceptually same code, in sh:
hex () {
out=""
while [ $1 -ne 0 ]; do
c=$(($1 % 16))
case $c in
10) c=a ;; 11) c=b ;; 12) c=c ;;
13) c=d ;; 14) c=e ;; 15) c=f ;;
esac
out=$c$out
set -- $(($1 / 16))
done
echo $out
}
hex 55
it seems single(i) is trying to match with double digit numeric (10 is one of of the value in array)
STATE 1
abc#xyz $ i=1
abc#xyz $ allStatus=(2 3 4 5 6 7 8 9 10)
abc#xyz $ if [[ ! ${allStatus[#]} =~ $i ]]
> then
> echo OK
> fi
STATE 2
abc#xyz $ i=2
abc#xyz $ allStatus=(1 3 4 5 6 7 8 9 10)
abc#xyz $
abc#xyz $ if [[ ! ${allStatus[#]} =~ $i ]]
> then
> echo OK
> fi
OK
abc#xyz $
all i want is, STATE 1 should echo/print OK
That's because the value after =~ is interpreted as a regular expression, and the string 10 matches the regular expression 1.
You need to check that there's a space or string start before the value and space or string end after the value:
[[ ${arr[#]} =~ ( |^)$i( |$) ]]
This can still fail if the value a 1 b belongs to the array:
foStatus=(2 3 'a 1 b')
The correct way is to iterate over the values and check for equality:
arr=(2 3 1 4 9 10 'a 1 b')
i=1
for v in "${arr[#]}" ; do
if [[ $v == "$i" ]] ; then
echo OK
fi
done
Since you are checking a numeric status exist, you can use a sparse array index and do:
#!/usr/bin/env bash
i=2
allStatus=([1]=1 [3]=1 [4]=1 [5]=1 [6]=1 [7]=1 [8]=1 [9]=1 [10]=1)
if ((allStatus[i])); then
echo OK
fi
You can also create a bit-field and check bit is on:
#!/usr/bin/env bash
i=2
allStatus=(1 3 4 5 6 7 8 9 10)
# Set all bits corresponding to status by expanding array elements
# into the arithmetic expression:
# 0 | 1 << element1 | 1 << element2 |...
allStatusBin=$((0${allStatus[#]/#/|1<<}))
# Or set it directly in binary
#allStatusBin=$((2#11111111010))
if ((1<<i&allStatusBin)); then
echo OK
fi
I want to use the tr command to map chars to new chars, for example:
echo "hello" | tr '[a-z]' '[b-za-b]' Will output: ifmmp
(where each letter in the lower-case alphabet is shifted over one to the right)
See below the mapping to new chars for '[b-za-b]':
[a b c d e f g h i j k l m n o p q r s t u v w x y z] will map to:
[b c d e f g h i j k l m n o p q r s t u v w x y z a]
However, when I want it to rotate multiple times, how can I use a variable to control the rotate-value for the tr command?
Eg: for a shift of 1:
echo "hello" | tr '[a-z]' '[b-za-b]' without variables and:
echo "hello" | tr '[a-z]' '[(a+$var)-za-(a+$var)]' where $var=1
here I have: (a+$var)-z representing the same as b-z and
....................a-(a+$var) representing the same as a-b
I have tried converting the ascii value to a char to use within the tr command but I don't think that is allowed.
My problem is that bash is not interpreting:
(a+$var) as the char b when $var=1
(a+$var) as the char c when $var=2
... etc.
How can I tell bash to interpret these equations as chars for the tr command
EDIT
I tried doing it with an array but it's not working:
chars=( {a..z} )
var=2
echo "hello" | tr '[a-z]' '[(${chars[var]}-z)(a-${chars[var]})]'
I used: (${chars[var]}-z) to represent b-z where var=1
Because ${chars[1]} is b but this is not working. Am I missing something?
What you are trying to do cannot be done using tr which does not handle your requirement. Moreover when you meant to modify and use variables to add to glob patterns in bash which is something you cannot possibly do.
There is a neat little trick you can do with bash arrays!. The tr command can take expanded array sequence over the plain glob patterns also. First define a source array as
source=()
Now add its contents as a list of character ranges from a-z using brace expansion as
source=({a..z})
and now for the transliterating array, from the source array, construct it as follows by using the indices to print the array elements
trans=()
Using a trick to get the array elements from the last with syntax ${array[#]: (-num)} will get you the total length - num of the elements. So building the array first as
var=2
trans+=( "${source[#]:(-(26-$var))}" )
and now to build the second part of the array, use another trick ${array[#]:0:num} to get the first num number of elemtents.
trans+=( "${source[#]:0:$(( $var + 1 ))}" )
So what we have done now is for a given value of var=2, we built the trans array as
echo "${trans[#]}"
c d e f g h i j k l m n o p q r s t u v w x y z a b c
Now you can just use it easily in the tr command
echo "hello" | tr "${source[*]}" "${trans[*]}"
jgnnq
You can just put it all in function and print its value as
transChar() {
local source
local trans
local result
source=({a..z})
trans=()
var="$2"
input="$1"
trans+=( "${source[#]:(-(26-$var))}" )
trans+=( "${source[#]:0:$(( $var + 1 ))}" )
result=$( echo "$input" | tr "${source[*]}" "${trans[*]}" )
echo "$result"
}
Some of the tests
transChar "hello" 1
ifmmp
transChar "hello" 2
jgnnq
transChar "hello" 3
khoor
rot-random:
# generate alphabet as arr:
arr=( {1..26} )
i=$(($RANDOM%24+1))
# left and right
l=$(echo ${arr[$i]})
r=$(echo ${arr[$i+1]})
# reusing arr for testing:
echo ${arr[#]} | tr "a-z" "$r-za-$l"
echo "secret:" | tr "a-z" "$r-za-$l" ; echo $l $r $i
amkzmb:
h i 7
You could use octal \XXX character codes for characters to do what you intend. Using the octal codes you could do any arithmetic manipulations to numbers and then convert them to character codes
# rotr x
#
# if 0 <= x <= 25 rotr x outputs a set specification
# that could be used as an argument to tr command
# otherwise it outputs 'a-z'
function rotr(){
i = $(( 97 + $1 ))
if [ $i -lt 97 ] ; then
translation='a-z'
elif [ $i -eq 97 ] ; then
translation='\141-\172' # 141 is the octal code for "a"
# 172 is the octal code for "z"
elif [ $i -eq 98 ] ; then
translation='\142-\172\141'
elif [ $i -lt 122 ] ; then # $i is between 99 and 121 ("c" and "y")
ii=$(echo "obase=8 ; $i" | bc)
jj=$(echo "obase=8 ; $(( $i - 1 ))" | bc)
translation=\\$ii'-\172\141-'\\$jj
elif [ $i -eq 122 ] ; then
translation='\172\141-\171'
else # $i > 122
tranlation='a-z'
fi
echo $translation
}
Now you could use this as follows
echo hello | tr 'a-z' $(rotr 7)
prints
olssv
Please tell why printing odd numbers in bash script with the following code gives the error:
line 3: {1 % 2 : syntax error: operand expected (error token is "{1 % 2 ")
for i in {1 to 99}
do
rem=$(( $i % 2 ))
if [$rem -neq 0];
then
echo $i
fi
done
This is working example:
for i in {1..99}
do
rem=$(($i % 2))
if [ "$rem" -ne "0" ]; then
echo $i
fi
done
used for loop have a typo in minimum and maximum number, should be {1..99} instead of {1 to 99}
brackets of the if statement needs to be separated with whitespace character on the left and on the right side
Comparision is done with ne instead of neq, see this reference.
As already pointed out, you can use this shell checker if you need some clarification of the error you get.
Not really sure why nobody included it, but this works for me and is simpler than the other 'for' solutions:
for (( i = 1; i < 100; i=i+2 )); do echo $i ; done
To print odd numbers between 1 to 99
seq 1 99 | sed -n 'p;n'
With GNU seq, credit to gniourf-gniourf
seq 1 2 99
Example
$ seq 1 10 | sed -n 'p;n'
1
3
5
7
9
if you reverse it will print even
$ seq 1 10 | sed -n 'n;p'
2
4
6
8
10
One liner:
for odd in {1..99..2}; do echo "${odd}"; done
Or print in a cluster.
for odd in {1..99..2}; do echo -n " ${odd} "; done
Likewise, to print even numbers only:
for even in {2..100..2}; do echo "${even}"; done
OR
for even in {2..100..2}; do echo -n " ${even} "; done
Replace {1 to 99} by {1..99}.
for (( i=1; i<=100; i++ ))
do
((b = $i % 2))
if [ $b -ne 0 ]
then
echo $i
fi
done
for i in {1..99}
do
rem=`expr $i % 2`
if [ $rem == 1 ]
then
echo "$i"
fi
done
for i in {0..49}
do
echo $(($i*2+1))
done
Assuming that I have a string like this one:
string="1 0 . # 1 1 ? 2 2 4"
Is it possible to concatenate digits that are next to each other?
So that string be like: 10 . # 11 ? 224 ?
I found only basic things how to distinguish integers from other characters and how to "connect" them. But I have no idea how to iterate properly.
num=""
for char in $string; do
if [ $char -eq $char 2>/dev/null ] ; then
num=$num$char
Here's an almost pure-shell implementation -- transforming the string into a character per line and using a BashFAQ #1 while read loop.
string="1 0 . # 1 1 ? 2 2 4"
output=''
# replace spaces with newlines for easier handling
string=$(printf '%s\n' "$string" | tr ' ' '\n')
last_was_number=0
printf '%s\n' "$string" | {
while read -r char; do
if [ "$char" -eq "$char" ] 2>/dev/null; then # it's a number
if [ "$last_was_number" -eq "1" ]; then
output="$output$char"
last_was_number=1
continue
fi
last_was_number=1
else
last_was_number=0
fi
output="$output $char"
done
printf '%s\n' "$output"
}
To complement Charles Duffy's helpful, POSIX-compliant sh solution with a more concise perl alternative:
Note: perl is not part of POSIX, but it is preinstalled on most modern Unix-like platforms.
$ printf '%s\n' "1 0 . # 1 1 ? 2 2 4" | perl -pe 's/\d( \d)+/$& =~ s| ||gr/eg'
10 . # 11 ? 224
The outer substitution, s/\d( \d)+/.../eg, globally (g) finds runs of at least 2 adjacent digits (\d( \d)+), and replaces each run with the result of the expression (e) specified as the replacement string (represented as ... here).
The expression in the inner substitution, $& =~ s| ||gr, whose result is used as the replacement string, removes all spaces from each run of adjacent digits:
$& represents what the outer regex matched - the run of adjacent digits.
=~ applies the s call on the RHS to the LHS, i.e., $& (without this, the s call would implicitly apply to the entire input string, $_).
s| ||gr replaces all (g) instances of <space> from the value of the value of $& and returns (r) the result, effectively removing all spaces.
Note that | is used arbitrarily as the delimiter character for the s call, so as to avoid a clash with the customary / delimiter used by the outer s call.
POSIX compliant one-liner with sed:
string="1 0 . # 1 1 ? 2 2 4"
printf '%s\n' "$string" | sed -e ':b' -e ' s/\([0-9]\) \([0-9]\)/\1\2/g; tb'
It just iteratively removes the any space between two digits until there aren't any more, resulting in:
10 . # 11 ? 224
Here is my solution:
string="1 0 . # 1 1 ? 2 2 4"
array=(${string/// })
arraylength=${#array[#]}
pattern="[0-9]"
i=0
while true; do
str=""
start=$i
if [ $i -eq $arraylength ]; then
break;
fi
for (( j=$start; j<${arraylength}; j++ )) do
curr=${array[$j]}
i=$((i + 1))
if [[ $curr =~ $pattern ]]; then
str="$str$curr"
else
break
fi
done
echo $str
done