bash shell script error: ambigious redirect - arrays

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.)

Related

Parametrized population of a string in bash from 1 to n

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?.

How to return an array from read command in a function?

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.

Changing an array in Perl

I am trying to use Perl to parse output from a (C-based) program.
Every output line is a (1D) Perl array, which I sometimes want to store (based on certain conditions).
I now wish to (deep) copy an array when its first element has a certain keyword,
and print that same copied array if another keyword matches in a later line-array.
So far, I have attempted the following:
#!/usr/bin/env perl
use strict; # recommended
use Storable qw(dclone);
...
while(1) # loop over the lines
{
# subsequent calls to tbse_line contain
# (references to) arrays of data
my $la = $population->tbse_line();
my #copy;
my $header = shift #$la;
# break out of the loop:
last if ($header eq 'fin');
if($header eq 'keyword')
{
#copy = #{ dclone \#$la };
}
if($header eq 'other_keyword')
{
print "second condition met, print first line:\n"
print "#copy\n";
}
}
However, this prints an empty line to the screen, instead of the contents of the copied array. I don't have a lot of Perl experience, and I can't figure out what I am doing wrong.
Any idea on how to go about this?
my #copy allocates a new Perl array named #copy in the current scope. It looks like you want to set #copy during one iteration of your while loop and print it in a different iteration. In order for your array not to be erased each time a new while loop iteration starts, you should move the my #copy declaration outside of the loop.
my #copy;
while (1) { ... }

Print regex match Perl

Hello I am in the process of making a program that matches a given set of keywords to a file.
I want to output the matched data to a text file and include the regex keyword that triggered the match.
Below is my code related to my issue:
my $counter = 0;
foreach($words)
{
while($line = <FILE>)
{
if($line =~ /$words/)
{
print "#array[$counter] $line\n";
print OUTPUT $line;
}
}
$counter ++;
}
This does not produce the expected outcome. It works perfectly for the first element in the array but for the rest it just simply prints the first one again. I believe the counter is not being incremented.
Is there a better / easier way to get the current element being used in the loop? or even get the current regex match?
The problem is that <FILE> exhausts the file for the first word. For the next word, <FILE> tries to read at the end of the file, which means the whole loop is skipped.
You can iterate over the words inside the loop over the file, or you can seek
back to the beginning of the file at the end of the loop.
Here is what you should do:
use strict;
use warnings;
use 5.016;
my $fname = 'data.txt';
my #patterns = (
'do.',
'.at',
'.ir.',
);
open my $INFILE, '<', $fname
or die "Couldn't read from $fname: $!";
while (my $line = <$INFILE>) {
for my $pattern (#patterns) {
if ($line =~ /($pattern)/) {
print "$pattern --> $1";
}
}
}
close $INFILE:
Putting parentheses around parts of the regex causes perl to set the match variables $1, $2, $3, etc., which contain the match for each parenthesized group.
$line will have a newline at the end of the line, so if you write print "$line\n", you will add another newline, so your output file will have blank lines between every line you print.

Perl - can't use string (...) as an array ref

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

Resources