How to write a for loop to perform an operation N times in the ash shell? - ash

I'm looking to run a command a given number of times in an Alpine Linux docker container which features the /bin/ash shell.
In Bash, this would be
bash-3.2$ for i in {1..3}
> do
> echo "number $i"
> done
number 1
number 2
number 3
However, the same syntax doesn't seem to work in ash:
> docker run -it --rm alpine /bin/ash
/ # for i in 1 .. 3
> do echo "number $i"
> done
number 1
number ..
number 3
/ # for i in {1..3}
> do echo "number $i"
> done
number {1..3}
/ #
I had a look at https://linux.die.net/man/1/ash but wasn't able to easily find out how to do this; does anyone know the correct syntax?

I ended up using seq with command substitution:
/ # for i in $(seq 10)
> do echo "number $i"
> done
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9
number 10

Simply like with bash or shell:
$ ash -c "for i in a b c 1 2 3; do echo i = \$i; done"
output:
i = a
i = b
i = c
i = 1
i = 2
i = 3

Another POSIX compatible alternative, which does not use potentially slow expansion, is to use
i=1; while [ ${i} -le 3 ]; do
echo ${i}
i=$(( i + 1 ))
done

Related

Subtract fixed number of days from date column using awk and add it to new column

Let's assume that we have a file with the values as seen bellow:
% head test.csv
20220601,A,B,1
20220530,A,B,1
And we want to add two new columns, one with the date minus 1 day and one with minus 7 days, resulting the following:
% head new_test.csv
20220601,A,B,20220525,20220531,1
20220530,A,B,20220523,20220529,1
The awk that was used to produce the above is:
% awk 'BEGIN{FS=OFS=","} { a="date -d \"$(date -d \""$1"\") -7 days\" +'%Y%m%d'"; a | getline st ; close(a) ;b="date -d \"$(date -d \""$1"\") -1 days\" +'%Y%m%d'"; b | getline cb ; close(b) ;print $1","$2","$3","st","cb","$4}' test.csv > new_test.csv
But after applying the above in a large file with more than 100K lines it runs for 20 minutes, is there any way to optimize the awk?
One GNU awk approach:
awk '
BEGIN { FS=OFS=","
secs_in_day = 60 * 60 * 24
}
{ dt = mktime( substr($1,1,4) " " substr($1,5,2) " " substr($1,7,2) " 12 0 0" )
dt1 = strftime("%Y%m%d",dt - secs_in_day )
dt7 = strftime("%Y%m%d",dt - (secs_in_day * 7) )
print $1,$2,$3,dt7,dt1,$4
}
' test.csv
This generates:
20220601,A,B,20220525,20220531,1
20220530,A,B,20220523,20220529,1
NOTES:
requires GNU awk for the mktime() and strftime() functions; see GNU awk time functions for more details
other flavors of awk may have similar functions, ymmv
You can try using function calls, it is faster than calling the .
awk -F, '
function cmd1(date){
a="date -d \"$(date -d \""date"\") -1days\" +'%Y%m%d'"
a | getline st
return st
close(a)
}
function cmd2(date){
b="date -d \"$(date -d \""date"\") -7days\" +'%Y%m%d'"
b | getline cm
return cm
close(b)
}
{
$5=cmd1($1)
$6=cmd2($1)
print $1","$2","$3","$5","$6","$4
}' OFS=, test > newFileTest
I executed this against a file with 20000 records in seconds, compared to the original awk which took around 5 minutes.

How to use grep command inside Tcl script

How to run simple grep command in a tcl script and get output
grep B file1 > temp # bash grep command need to execute inside tcl commad,
file1 looks like this:
1 2 3 6 180.00 B
1 2 3 6 F
2 3 6 23 50.00 B
2 3 6 23 F
these do not work
exec grep B file.txt > temp
child process exited abnormally
exec "grep B pes_test.com > temp1"
couldn't execute "grep -e B ./pes_test.com > temp1": no such file or directory
exec /bin/sh -c {grep -e B ; true} < pes_test.com > tmp1
works but do not gives output,
exec throws an error when the process returns non-zero. See exec and the Tcl wiki
try {
set result [exec grep $pattern $file]
} on error {e} {
# typically, pattern not found
set result ""
}
Ref: try man page

Column width of mysql output

The output of an mysql command doesnt correctly align the titels (using script).
Here is the sql command in script.sh
#!/usr/bin/bash
echo "select * from hw_inventory "| mysql --host=localhost --user=root --database=monitor > /tmp/inventory
This gives me following output in /tmp/inventory
ip_address entity_index entity_physname entity_physdesc entity_serial
10.212.0.1 1000 Switch 1 WS-C3850-12S FOC1842U117
10.212.0.1 1009 Switch 1 - Power Supply A Switch 1 - Power Supply A LIT18300URD
10.212.0.1 1010 Switch 1 - Power Supply B Switch 1 - Power Supply B LIT183506NH
10.212.0.1 1034 Switch 1 FRU Uplink Module 1 2x1G 2x10G Uplink Module FOC18363NJX
As you cen see the alignment (tabs) is not in the same way as the text with Switch 1 should start under entity_physname.
It needs to be like following output:
ip_address entity_index entity_physname entity_physdesc entity_serial
10.212.0.1 1000 Switch 1 WS-C3850-12S FOC1842U117
10.212.0.1 1009 Switch 1 - Power Supply A Switch 1 - Power Supply A LIT18300URD
10.212.0.1 1010 Switch 1 - Power Supply B Switch 1 - Power Supply B LIT183506NH
10.212.0.1 1034 Switch 1 FRU Uplink Module 1 2x1G 2x10G Uplink Module FOC18363NJX
Any ideas?
Thanks in advance
For formatting the output from the mysql use the -t param
"select * from hw_inventory "| mysql -t --host=localhost --user=root --database=monitor > /tmp/inventory

Awk: How to cut similar part of 2 fields and then get the difference of remaining part?

Let say I have 2 fields displaying epoch time in microseconds:
1318044415123456,1318044415990056
What I wanted to do is:
Cut the common part from both fields: "1318044415"
Get the difference of the remaining parts: 990056 - 123456 = 866600
Why am I doing this? Because awk uses floating point IEEE 754 but not 64 bit integers and I need to get difference of epoch time of 2 events in microseconds.
Thanks for any help!
EDIT:
Finally I found the largest number Awk could handle on Snow Leopard 10.6.8: 9007199254740992.
Try this: echo '9007199254740992' | awk -F ',' '{print $1 + 0}'
The version of Awk was 20070501 (produced by awk --version)
Here is an awk script that meets your requirements:
BEGIN {
FS = ","
}
{
s1 = $1
s2 = $2
while (length(s1) > 1 && substr(s1, 1, 1) == substr(s2, 1, 1))
{
s1 = substr(s1, 2)
s2 = substr(s2, 2)
}
n1 = s1 + 0
n2 = s2 + 0
print n2 - n1
}

How can I generate a file of random negative and positive integers in serial?

I want a file of randomly generated positive or negative serial integers. For now, I ask the file contain roughly (no guarantee required) equal numbers of negative and positive, but make it easy to change the proportions later. By "serial", I mean the kth random negative is equal to -k, and the kth random positive is equal to +k.
This GNU Bash script one-liner would satisfy the file format, but just wouldn't be random.
$ seq -1 -1 -5 && seq 1 5
-1
-2
-3
-4
-5
1
2
3
4
5
This example shows what I'm looking for even better, but is still not random since the integers alternate predictably between negative and positive.
$ paste <(seq -1 -1 -5) <(seq 1 5) | tr '\t' '\n'
-1
1
-2
2
-3
3
-4
4
-5
5
Sending one of these through the shuf command makes them randomly negative or positive, but they lose their serial-ness.
$ paste <(seq -1 -1 -5) <(seq 1 5) | tr '\t' '\n' | shuf
-5
4
3
2
-2
1
-1
-4
5
-3
Note: I'm trying to test algorithms that sort lists/arrays of bits (zeros and ones), but if I use 0s and 1s I won't be able to analyse the sort's behaviour or tell if stability was preserved.
If I understand correctly, you want to interleave the positive integers and the negative integers randomly. For example: 1 2 -1 3 -2 4 5- 3.
my $count = 10;
my $pos = 1;
my $neg = -1;
my #random = map {
int(rand 2)
? $pos++
: $neg--
} 1..$count;
print "#random\n";
Update:
To change proportions I'd do this:
use strict;
use warnings;
my $next = get_list_generator(.5);
my #random = map $next->(), 1..10;
print "#random\n";
my $again = get_list_generator(.25);
my #another = map $again->(), 1..10;
print "#another\n";
sub get_list_generator {
my $prob_positive = shift;
my $pos = 1;
my $neg = -1;
return sub {
return rand() <= $prob_positive ? scalar $pos++ : scalar $neg--;
}
}
The get_list_generator() function returns a closure. This way you can even have multiple list generators going at once.
Let's start golf contest? (44)
perl -le'print rand>.5?++$a:--$b for 1..10'
Edit: daotoad's 40 chars version
seq 1 10|perl -ple'$_=rand>.5?++$a:--$b'
Where 15 is the total amount of numbers generated and tp is the amount of positive numbers you want (effectively indicating the ratio of pos/neg):
tp=8
unset p n
for i in $(printf '%s\n' {1..15} | gsort -R); do
(( i <= tp )) && \
echo $((++p)) || \
echo $((--n))
done
#!/bin/bash
pos=0 neg=0
for i in {1..10}
do
if (( ($RANDOM > 16384 ? ++pos : --neg) > 0 ))
then echo $pos
else echo $neg
fi
done
I could not quite fit this into a one-liner. Anyone else?
edit: Ah, a one liner, 65 characters (need to set a and b if you're repeatedly invoking this in the same shell):
a=0 b=0;for i in {1..10}; do echo $(($RANDOM>16384?++a:--b));done
Here's a Bash one-liner (2?) inspired by lhunath and Brian's answers.
RANDOM=$$; pos=1; neg=-1; for i in {1..10}; do \
echo $(( $(echo $RANDOM / 32767 \> 0.5 | bc -l) ? pos++ : neg-- )); done
Here's an Awk script that competes in the golf contest (44).
seq 1 10|awk '{print(rand()>0.5?++p:--n);}'
This is the clearer idiomatic way to write it:
seq 1 10 | awk 'BEGIN{srand(); pos=1; neg=-1;}
{print (rand() > 0.5 ? pos++ : neg--);}'
There is no set of numbers that will fit all your criterion. You can't say you want random but at the same time say that the kth negative value == -k and the kth positive value == k. You can either have it random, or not.
As to what you're trying to do, why not separate the two concerns and test the sort on something like an array of pairs of integers length n. The first of the pair can be zero or 1 and the second of the pair will be your stability tracker (just a count from 0 to n).
Generate the list of 0's and 1's that you want and shuffle them then add on the tracker integer. Now sort the pairs by their first element.
The input to your sort will look something like this.
0, 1
1, 2
0, 3
1, 4
1, 5
0, 6
0, 7
1, 8
1, 9
1, 10
0, 11
0, 12
0, 13
Stable sorts will produce this
0, 1
0, 3
0, 6
0, 7
0, 11
0, 12
0, 13
1, 2
1, 4
1, 5
1, 8
1, 9
1, 10
Unstable ones will produce the 0's and 1's with the tracker integers out of order.