Is there an easy way to populate a dynamic string with a size parameter?
lets say, we have:
Case N=1:
echo "Benchmark,Time_Run1" > $LOGDIR/$FILENAME
however, the run variable is parametric and we want to have all Time_Runs from 1 to n:
Case N=4:
echo "Benchmark,Time_Run1,Time_Run2,Time_Run3,Time_Run4" > $LOGDIR/$FILENAME
and the generic solution should be this form:
Case N=n:
echo "Benchmark,Time_Run1,...,Time_Run${n}" > $LOGDIR/$FILENAME
Is there a way to do that in a single loop rather than having two loops, one looping over n to generate the Run${n} and the other, looping n times to append "Time_Run" to the list (similar to Python)? Thanks!
Use a loop from 1 to $n.
{
printf 'Benchmark'
for ((i = 1; i <= $n; i++)); do
printf ',Time_Run%d' $i
done
printf '\n'
} > $LOGDIR/$FILENAME
One way to populate the output string with a single loop is:
outstr=Benchmark
for ((i=1; i<=n; i++)); do
outstr+=",Time_Run$i"
done
It can also be done without a loop:
eval "printf -v outstr ',%s' Time_Run{1..$n}"
outstr="Benchmark${outstr}"
However, eval is dangerous and should be used only in cases where there is no reasonable alternative. This is not such a case. See Why should eval be avoided in Bash, and what should I use instead?.
Related
Let's say I have a file with the lines such as:
*some numbers* :00: *somenumbers*
*somenumbers* :21: *somenumbers*
And for every number between :: I need to count how many times it repeats in the file?
while (<>){
chomp($_);
my ($nebitno,$bitno,$opetnebitno) = split /:/, $_;
$count{$bitno}++;
}
foreach $bitno(sort keys %count){
print $bitno," ",$count{bitno}, "\n";
}
What you produced was not bad code — it did the job for a single file at a time. Adapting the code shown in the question to handle multiple files, resetting the counts after each file:
#!/usr/bin/perl
use strict;
use warnings;
my %count = ();
while (<>) {
my ($nebitno, $bitno, $opetnebitno) = split /:/, $_;
$count{$bitno}++;
}
continue
{
if (eof) {
print "$ARGV:\n";
foreach $bitno (sort keys %count) {
print "$bitno $count{bitno}\n";
}
%count = ();
}
}
The key here is the continue block, and the if (eof) test. You can use close $ARGV in a continue block to reset $. (the line number) when the file changes; it is a common use for it. This sort of per-file summary is another use. The other changes are cosmetic. You don't need to chomp the line (though there's no particular harm done if you do); I print whole strings rather than using comma-separated lists (it works well here and very often). I use a few more spaces. I left it with the 1TBS format for the blocks of code, though I don't use that myself (I use Allman).
My draft solution used practically the same printing code as shown above, but the main while loop was slightly different:
#!/usr/bin/env perl
use strict;
use warnings;
my %counts = ();
while (<>)
{
$counts{$1}++ if (m/.*:(\d+):/);
}
continue
{
if (eof)
{
print "$ARGV:\n";
foreach my $number (sort { $a <=> $b } keys %counts)
{
print ":$number: $counts{$number}\n"
}
%counts = ();
}
}
The only advantage over what you used is that if some line doesn't contain a colon-surrounded number, it ignores the line, whereas yours doesn't consider that possibility. I'm not sure the comparison code in the sort is necessary — it ensures that the comparisons are numeric, though. If the numbers are all the same length and zero-padded on the left when necessary, there's no problem. If they're more generally formatted, the 'forced numeric' comparison might make a difference.
Remember: this is Perl, so TMTOWDTI (There's More Than One Way To Do It). Someone else might come up with a simpler solution.
Desired output can be achieved with following code snippet
look for pattern :\d+: in a line
increment hash %count for the digit
output result to console
use strict;
use warnings;
use feature 'say';
my %count;
/:(\d+):/ && $count{$1}++ for <>;
say "$_ = $count{$_}" for sort keys %count;
I have a small problem in here with bash
I wrote an array in a simple function and I need to return it as an array with read command and also need to call it somehow.
function myData {
echo 'Enter the serial number of your items : '
read -a sn
return ${sn[#]}
}
for example like this ???
$ ./myapp.sh
Enter the serial number of your items : 92467 90218 94320 94382
myData
echo ${?[#]}
Why we don't have return value in here like other languages ?
thanks for your help...
As others mention, the builtin command return is intended to send the exit status to the caller.
If you want to pass the result of processing in the function to the
caller, there will be several ways:
Use standard output
If you write something to the standard output within a function, the output
is redirected to the caller. The standard output is just a non-structured
stream of bytes. If you want to make it have a special meaning such as an
array, you need to define the structure by assigning a delimiter to some
character(s). If you are sure each element do not contain space, tab, or
newline, you can rely on the default value of IFS:
myfunc() {
echo "92467 90218 94320 94382"
}
ary=( $(myfunc) )
for i in "${ary[#]}"; do
echo "$i"
done
If the elements of the array may contain whitespace or other special
characters and you need to preserve them (such a case as you are handling
filenames), you can use the null character as the delimiter:
myfunc() {
local -a a=("some" "elements" "contain whitespace" $'or \nnewline')
printf "%s\0" "${a[#]}"
}
mapfile -d "" -t ary < <(myfunc)
for i in "${ary[#]}"; do
echo ">$i" # The leading ">" just indicates the start of each element
done
Pass by reference
As other languages, bash>=4.3 has a mechanism to pass the variable by
reference or by name:
myfunc() {
local -n p="$1" # now p refers to the variable with the name of value of $1
for (( i=0; i<${#p[#]}; i++ )); do
((p[i]++)) # increment each value
done
}
ary=(0 1 2)
myfunc "ary"
echo "${ary[#]}" # array elements are modified
Use the array as a global variable
Will be needless to explain its usage and pros/cons.
Hope this helps.
In the following code, a problem occurs when encapsulating the block of code with a function. The error message $1: ambigious redirect" is displayed.
The while loop reads text from the input.txt file line by line and stores it in the array linesArray.
The for loop within the 'while' loop iterates through all the elements of the linesArray array at index 0, and then executes the case-statement, which compares the elements of the array with valid mips operators.
function mnemonicCheck {
while read line; do
linesArray=($line)
for e in ${linesArray[0]}
do
case $e in
"add")
;;
"sub")
;;
"addi")
;;
"lw")
;;
"sw")
;;
*)
echo "invalid operator used. Valid operators are: add,sub,addi,lw,sw"
;;
esac
done
done < $1
}
#calling methods
checkArguments $1 $2 $3
mnemonicCheck
You call mnemonicCheck without any arguments, so inside the function $1 is the empty string.
What would you then expect
< $1
to mean? (I agree that "ambiguous" is an odd word to use in this context, but it's not entirely incorrect since one cannot say for certain what is meant.)
I'm practicing Perl with a challenge from codeeval.com, and I'm getting an unexpected error. The goal is to iterate through a file line-by-line, in which each line has a string and a character separated by a comma, and to find the right-most occurrence of that character in the string. I was getting wrong answers back, so I altered the code to print out just variable values, when I got the following error:
Can't use string ("Hello world") as an ARRAY ref while "strict refs" in use at char_pos.pl line 20, <FILE> line 1.
My code is below. You can see a sample from the file in the header. You can also see the original output code, which was incorrectly only displaying the right-most character in each string.
#CodeEval challenge: https://www.codeeval.com/open_challenges/31/
#Call with $> char_pos.pl numbers
##Hello world, d
##Hola mundo, H
##Keyboard, b
##Connecticut, n
#!/usr/bin/perl
use strict;
use warnings;
my $path = $ARGV[0];
open FILE, $path or die $!;
my $len;
while(<FILE>)
{
my #args = split(/,/,$_);
$len = length($args[0]) - 1;
print "$len\n";
for(;$len >= 0; $len--)
{
last if $args[0][$len] == $args[1];
}
#if($len > -1)
#{
# print $len, "\n";
#}else
#{
# print "not found\n";
#}
}
EDIT:
Based on the answers below, here's the code that I got to work:
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
open my $fh,"<",shift;
while(my $line = <$fh>)
{
chomp $line;
my #args = split(/,/,$line);
my $index = rindex($args[0],$args[1]);
print $index>-1 ? "$index\n" : "Not found\n";
}
close $fh;
It looks like you need to know a bit about Perl functions. Perl has many functions for strings and scalars and it's not always possible to know them all right off the top of your head.
However, Perl has a great function called rindex that does exactly what you want. You give it a string, a substring (in this case, a single character), and it looks for the first position of that substring from the right side of the string (the index does the same thing from the left hand side.)
Since you're learning Perl, it may be a good idea to get a few books on Modern Perl and standard coding practices. This way, you know newer coding techniques and the standard coding practices.
Modern Perl - Gives you newer programming help.
Learning Perl - An old standard.
Perl Best Practices - The standard coding practices.
Here's a sample program:
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
use feature qw(say);
open my $fh, "<", shift;
while ( my $line = <$fh> ) {
chomp $line;
my ($string, $char) = split /,/, $line, 2;
if ( length $char != 1 or not defined $string ) {
say qq(Invalid line "$line".);
next;
}
my $location = rindex $string, $char;
if ( $location != -1 ) {
say qq(The right most "$char" is at position $location in "$string".);
}
else {
say qq(The character "$char" wasn't found in line "$line".)";
}
close $fh;
A few suggestions:
use autodie allows your program to automatically die on bad open. No need to check.
Three parameter open statement is now considered de rigueur.
Use scalar variables for file handles. They're easier to pass into subroutines.
Use lexically scoped variables for loops. Try to avoid using $_.
Always do a chomp after a read.
And most importantly, error check! I check the format of the line to make sure that's there is only a single comma, and that the character I'm searching for is a character. I also check the exit value of rindex to make sure it found the character. If rindex doesn't find the character, it returns a -1.
Also know that the first character in a line is 0 and not 1. You may need to adjust for this depending what output you're expecting.
Strings in perl are a basic type, not subscriptable arrays. You would use the substr function to get individual characters (which are also just strings) or substrings from them.
Also note that string comparison is done with eq; == is numeric comparison.
while($i=<DATA>){
($string,$char)=split(",",$i);
push(#str,$string);}
#join=split("",$_), print "$join[-1]\n",foreach(#str);
__DATA__
Hello world, d
Hola mundo, H
Keyboard, b
Connecticut, n
I tried to iterate over two arrays (with two nested for loops) using bash.
Unfortunatly bash is really slow by iterating over large arrays. So I tried to use awk.
First
I am reading in two files (around 200.000 lines) and take the tab seperated column I wanna use
START=($(awk -F'\t' '{print $5}' $inputGenes))
I was always thinking that START is now something like an array, but right now I'm not sure any more.
I have a lot of different "arrays" and go to the next step
Second
Everything is working fine with small files and not using awk, but a normal nested bash loop.
Now I was trying to use awk and I fail.
The two variables $len and $varlen are indicating the size of two arrays (read in like before using awk)
len=${#posVCF[#]}
The loops are working but I get no output, because it is not possible to get the information out of the arrays : $posVCF[$i] returns nothing. But I have no idea how to get information out of my arrays variables.
**echo | awk 'BEGIN {for(i=1; i -st $len; i++) {
for (j=1; j -st $varlen; j++) {
if ($posVCF[$i] -gt $START[$j] && $posVCF[$i] -st $END[$j]) {
print $posVCF[$i] " > " $START[$j] " und < " $END[$j]
}
}
}
}'**
Am I doing something wrong by reading the files or do you have any ideas? I'm really new in programming in bash, but I have to write in bash.
I hope you can help me, thank you very much.
You need braces to dereference an array element. Not $posVCF[$i] but ${posVCF[$i]} is correct.
I misread your question. Why do you think you need awk? All your variables are in the shell. You can use C-like for loops in bash:
for ((i=1; i < len; i++)); do
for ((j=1; j < varlen; j++)); do
if (( ${posVCF[$i]} > ${START[$j]} && ${posVCF[$i]} < ${END[$j]} )); then
echo ${posVCF[$i]} " > " ${START[$j]} " und < " ${END[$j]}
fi
done
done
This is using the bash arithmetic evaluation syntax: (( ... ))