Converting from for loop to while if fi loop UNIX - bash

I'm trying to figure out how to convert the following for loop into a while loop or until loop including if fi for UNIX Here's the code...
#!/bin/bash
if [ $# -gt 0 ] ; then
start=$1
end=$3
step=$2
for x in `seq $start $step $end` ; do
number $x
done
else
echo Enter at least one number on the command line.
fi
Any help is greatly appreciated!

(( x = start ))
until (( x > end ))
do
number $x
(( x += step ))
done

For a loop like this, it's easiest to use the numeric-style for loop:
for ((x=start; x<=end; x+=step)); do
number $x
done

Using seq is good as it has some default values for step and end value. I added this to the if if only one, or two arguments are given. Also it handles the situation if the given argument are not numbers (default values are 0). Also an endless loop may encounter if step is zero. If You remove this checking it will work the same way as the seq version.
if [ $# -gt 0 ] ; then
x=$(($1+0))
end=$((${3:-$x}+0))
step=$((${2:-1}+0))
[ $step -eq 0 ] && echo "Step is zero!" && exit 1
while [ $x -le $end -a $step -gt 0 -o $x -ge $end -a $step -lt 0 ]; do
echo $x
x=$((x+step))
done
else
echo Enter at least one number on the command line.
fi
Change echo to number in the while loop if you would like to use!
Examples:
$ ./e.sh 1
1
$ seq 1
1
$ ./e.sh 1 1 3
1
2
3
$ seq 1 1 3
1
2
3
$ ./e.sh 1 -1 3
$ seq 1 -1 3
$ ./e.sh 3 -1 1
3
2
1
$ seq 3 -1 1
3
2
1
$ ./e.sh 1 0 1
Step is zero!
$ seq 1 0 1|head
1
1
1
... (endlessly)
$ ./e.sh 1 .1 1.2
./e.sh: line 3: 1.2+0: syntax error: invalid arithmetic operator (error token is ".2+0")
$ seq 1 .1 1.2
1.0
1.1
1.2
Bash arithmetic is not working with floating point numbers, but seq does. If You need this feature You could use the x=$(echo $x + $step|bc) format. Also $((...)) should replaced by $(...|bc) like lines.

In Bash 4 you can also do it like this:
for i in {$start..$stop..$step}; do
echo $i
done

Related

numbers as command-line arguments and prints the count

I want to take numbers as command-line arguments and prints the count of
numbers that end with 0, 1, 2, etc. up to 5.
Example:
bash test.sh 12 14 12 15 14
Expected Output:
Digit_ends_with count
0 0
1 0
2 2
3 0
4 2
5 1
Mt Attempt:
read -a integers for i in ${integers[#]} do if [[ grep -o '[0-9]' $i ]] count=$(grep -c $i) if [ "$count" -ge "0" ] then
echo "Digit_ends_with" $i echo -e "Count ""$count" fi fi done
But this is not working. How I can achieve this requirement?
#!/bin/bash
echo "Digit_ends_with count" #print table header
for argument in "$#" #loop over all given arguments
do
current=$(echo "$argument" | tail -c 2 ) #get last digit
if (( "$current" <= 5 )) #check if lower than 6
then
echo "$current" #echo if true
fi
done | sort | uniq -c | sed -E 's/\s+//' | sed -E 's/([0-9]+).?([0-9]+)/\2\t\t\1/' #sort, count, remove leading spaces and switch the fields
Example:
╰─$ ./test.sh 188 182 182 12 13 14 18 15 16 17 18 19 10 0 0 0 0 0 0 0 0 0 0
Digit_ends_with count
0 11
2 3
3 1
4 1
5 1
Would you please try the following:
#!/bin/bash
for i in "$#"; do # loop over arguments
(( count[i % 10]++ )) # index with the modulo 10
done
printf "%s %s\n" "Digit_ends_with" "count" # header line
for (( i = 0; i < 6; i++ )); do # loop between 0 and 5
printf "%d\t\t%d\n" "$i" "${count[$i]}" # print the counts
done
Result of ./test.sh 12 14 12 15 14:
Digit_ends_with count
0 0
1 0
2 2
3 0
4 2
5 1

Bash parse list for prime numbers.

I want to parse a list of hexas that i get from a file after parsing them for unique hexas and sorting them.
The list looks like this:
1 0xb6e38000
8 0xb6f66000
5 0xb6f69000
1 0xb6f6c000
3 0xb6fd4000
1 0xb6ff7000
2 0xb6ff8000
4 0xb6ffa000
1 0xb6ffb000
Now what i want to do, is refine it even more so that i get only the hexas with prime numbers in front, like this:
1 0xb6e38000
5 0xb6f69000
1 0xb6f6c000
3 0xb6fd4000
1 0xb6ff7000
1 0xb6ffb000
The command i have been using is this:
sort | uniq -c | grep " 1 0x"
But this command lists only the ones that appear only once in the file.
Can anyone help me "sort" this out ?
Original answer for prime numbers
As mentioned in the comments to the question, 1 is not prime, but in the description of desired output you list the lines starting with 1. However, if you actually want to filter out the lines with prime numbers in the first column, then the following script will help:
#!/bin/bash -
[ $# -gt 0 ] && source_file="$1"
: ${source_file:=/tmp/source-file}
function is_prime {
declare -i n="$1"
if [ $n -le 1 ]; then
return 1
elif [ $n -le 3 ]; then
return 0
elif [[ $(( $n % 2 )) == 0 || $(( $n % 3 )) == 0 ]]; then
return 1
fi
i=5
while [[ $(( $i * $i )) -le $n ]]; do
if [[ $(( $n % $i )) == 0 || $(( $n % ($i + 2) )) == 0 ]]; then
return 1
fi
(( i += 6 ))
done
return 0
}
while read -a line; do
# We accept only two or more columns
[ ${#line[#]} -ge 2 ] || continue;
if is_prime ${line[0]}; then
echo $line
else
echo >&2 "skipping ${line[*]} as ${line[0]} is not prime"
fi
done < "$source_file"
In this script we define is_prime function which returns zero (success status), if the first argument ($1) is a prime number. Non-zero means non-prime number. The algorithm is a version of this pseudo-code translated into Bash.
Then we read $source_file line by line with the while loop where we put the columns into the line array: read -a line. Then we check if is_prime ${line[0]} command exits with a success code (zero) and output the line if it is. Otherwise, we print a message to the standard error (echo >&2).
The script accepts optional argument for the source file path. It assigns $source_file to /tmp/source-file, if the first argument is missing.
Script usage
Save the above-mentioned code in script.sh file.
Call bash script.sh /path/to/source-file >filtered 2>errors
Examine the contents of filtered and errors files. The first will contain the lines filtered out from the source file.
The output files will look like the following:
filtered
5 0xb6f69000
3 0xb6fd4000
2 0xb6ff8000
errors
skipping 1 0xb6e38000 as 1 is not prime
skipping 8 0xb6f66000 as 8 is not prime
skipping 1 0xb6f6c000 as 1 is not prime
skipping 1 0xb6ff7000 as 1 is not prime
skipping 4 0xb6ffa000 as 4 is not prime
skipping 1 0xb6ffb000 as 1 is not prime
Update for odd numbers
i sould have mentioned this from the start, i need odd number lines to
show up, not prime. My bad. – biotic
It's easy to detect if a number is odd with an expression like if [[ $(( $n % 2 )) != 0 ]]. The expression checks if the remainder by 2 is not equal to zero, i.e. applies the modulo operation. If the remainder is zero, then the number is even, otherwise odd, of course.
The full script:
#!/bin/bash -
[ $# -gt 0 ] && source_file="$1"
: ${source_file:=/tmp/source-file}
while read -a line; do
# We accept only two or more columns
[ ${#line[#]} -ge 2 ] || continue;
declare -i n=${line[0]}
if [[ $(( $n % 2 )) != 0 ]]; then
echo ${line[*]}
else
echo >&2 "skipping ${line[*]} as ${line[0]} is even"
fi
done < "$source_file"
As for above, you run bash script.sh /path/to/source-file >filtered 2>errors
Sample output:
filtered
1 0xb6e38000
5 0xb6f69000
1 0xb6f6c000
3 0xb6fd4000
1 0xb6ff7000
1 0xb6ffb000
errors
skipping 8 0xb6f66000 as 8 is even
skipping 2 0xb6ff8000 as 2 is even
skipping 4 0xb6ffa000 as 4 is even

Why won't my shell script work?

I'm using bash and this is my code
for i in $(seq 20)
do
if [ $i % 3 == 0 ]
then
echo HI
else
echo $i
fi
done
So the output should be:
1
2
hi
4
5
hi
...
and so on.
But I keep getting this error when I run my shell.
script.sh 4: [: 1: unexpected operator
1
then it just goes on like that until 20.
I've tried different things but i can't get it to work.
Even a bit simpler:
if ((i%3 == 0)) # No '$' needed
then
....
You need to change the line containing the if to:
if [[ $((i%3)) == 0 ]]
That will work.
Use this
for i in {1..20}
do
if [ `expr $i % 3` == 0 ]
then
echo HI
else
echo $i
fi
done
It should be like below mentioned format,
if [ $i%3 == 0 ];
then
but if you introduce "==" symbol it will only produce incremental value from 1 to 20.

Exporting BASH_XTRACEFD has no effect on subprocesses and it loses its special meaning

I have just noticed that exporting this converts it into an ordinary variable with no special meaning in subshells. Is this a security feature of bash?
This is a snippet to check the feature:
#! /bin/bash
exec 3>| trace.txt
BASH_XTRACEFD=3
set -x
# Something to trace
i=1 ; test "$i" -gt 2
# Now in a subshell
(i=2 ; test "$i" -gt 2)
# Let's export it
export BASH_XTRACEFD
# Again check trace
i=3 ; test "$i" -gt 2
# Now in a subshell
(i=4 ; test "$i" -gt 2)
And here is trace.txt:
+ i=1
+ test 1 -gt 2
+ i=2
+ test 2 -gt 2
+ export BASH_XTRACEFD
+ i=3
+ test 3 -gt 2
+ i=4
+ test 4 -gt 2
It works! May be we have different bash version. Mine is 4.3.11(1)-release (x86_64-pc-linux-gnu). What's yours?

Bash Scripting leave 4 digit numbers alone

EDIT: SOLVED. Thank you everyone!
I am building a shell script and I am stuck with comparing. I call it via ./myscript 1000, where 1000 is in a variable called y.
The problem is I need to make it so if I pass any 4 digit number to my script such as 0001 it will not be modified by my if statements, but when I call my script it is basically telling me 0001 and 1 are the same things. How can I check the size of $y to fix my problem?
I've tried echoing y, it does show 0001 and 1 when I pass them to my script but I do not know how to have any 4 digit number left alone.
####CODE SNIPP
#NOT EVEN NEEDED, JUST FOR SHOW, need to think of 0000 to any 4 digit #
#as different, but not sure how to do it.
if [ "$y" -ge 0000 ] && [ "$y" -le 9999 ]
then
#DO NOTHING
#IF Y <= 50 && Y >= 0
elif [ "$y" -le 50 ] && [ "$y" -ge 0 ]
then
#DO SOMETHING
#IF Y > 50 && Y <= 99
elif [ "$y" -gt 50 ] && [ "$y" -le 99 ]
then
#DO SOMETHING
fi
Does anyone have any tips on how I can tell me script 0001 and 1 are two different things? I figure bash must have something to check the input length or something but I cannot find anything.
Bash supports finding the length of a string stored in a variable.
A=hello
echo ${#A} # outputs "5"
You can use this within an if statement:
A=0001
if [ ${#A} -ne 4 ]; then
# stuff done if not four digits
fi
Note that bash will treat any arguments to -eq, -ne, -lt, -le, -gt or -ge as numbers.
If you want to treat them as a string, use = and !=
$ test 1 = 2; echo $?
1
$ test 1 = 1; echo $?
0
$ test 1 = 001; echo $?
1
$
Note how 1 and 001 are considered distinct. May this help you on your way.
If you really want to know long something is, try using wc?
$ echo -n abc | wc -c
3
$ y=0001
$ echo -n $y | wc -c
4
$ test `echo -n $y | wc -c` -eq 4; echo $?
0
$ y=1
$ test `echo -n $y | wc -c` -eq 4; echo $?
1
$
The last case returns 1 informing us that $y is not 4 characters long.
I'd identify the 4-digit numbers using case:
case $y in
([0-9][0-9][0-9][0-9])
is_4_digits=yes;;
(*) is_4_digits=no;;
esac
What else you do depends on your requirements - it could be that you do everything in the '(*)' clause; I'd use ': OK' in the 4-digit case to indicate that this case is OK:
case $y in
([0-9][0-9][0-9][0-9]) : OK;;
(*) # ... do all the other work here ...
;;
esac

Resources