Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

variable / dynamic variables - or suggestions.

Status
Not open for further replies.

twintriode

Technical User
Jan 6, 2005
21
0
0
AU
Hi All,

I am relatively new to programming and am having some trouble with the following issue. Logically it seems all that would need to be done would make a variable variable and my problem would be solved. Here is the relevant part of my code;
Code:
        @devices = ($device, @devices); }

	$numdev = $c - "2"; #where $c = 4 
	print "\nThere are $numdev devices available\n";

#assign values to the devices
	$d = 0;
	$e = $d + 1;
	while ($d < $numdev ){
		$button{$e}= $devices[$d];
		print "$e - $devices[$d]\n";
		$d++;
		$e++;
	}
I know now I am using a hash dynamic variable which does not work: $button{$e}= $devices[$d];

What I want is depending on how many devices end up in the @devices array, create the same amount of scalar variables, called:

$button1
$button2

where in this case;

$button1 = "eth1";
$button2 = "eth2";

So how can this be done??

Thanks
 
hmm, I guess I could just use the array variables:

$devices[0]
$devices[1]
$devices[n]

instead of trying to complicate things...
 
much wiser: Perl's soft references - what you call dynamic variables - are very much a weapon of last resort.

I'm worried by the rest of your code. Some of it is style-related - there's an awful lot of c-style indexing variables and counts which need very rarely appear in perl code - but my main worry is more basic. At the moment, the array of button names seems to be the array of device names, starting from index 1 rather than index 0. If that's the case, why have two arrays at all?

If you'd like to post more code and a better idea of what you're trying to achieve I can probably be more specific.

Yours,

fish

[&quot;]As soon as we started programming, we found to our surprise that it wasn't as easy to get programs right as we had thought. Debugging had to be discovered. I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.[&quot;]
--Maur
 
sub variable{1}
sub dynamic_variable{1}
sub redundant_redundant{1}
sub constant_var {"i am constant string - that should use single quotes..."}
if ($dynamic = variable() )
{ print "variables are dynamic\n" }
else
{
$variable = constant();
print "$variable is not dynamic\n";
}
print "ok\n" if dynamic_variable() == redundant_redundant();
# constant_var is a constant
# this may be an executably over perl-bose
# way of stating my point...
# also it is somewhat wrong as dynamic_variable and
# redundant_redundant are both constants as well... hmm
# somewhat misleading using = instead of == in if()'s is too
 
Hi Fishiface, thanks for the reply.

basically I am trying to write a small gtk-perl program stylized to my needs which aims to make accessing lots of different wireless networks easy.

The idea behind the question basically comes from the need to find all wireless devices available, then have the program respond accordingly, ie if there are 2 devices there will be two device selection buttons, if there are three devices there will be three selection buttons etc. This was alot easier in the straight perl version of my program, because I could just list the array parts until they ran out. Once you select the corresponding number that is printed next to the device, it just assigns my main active device variable that value. Like:

Code:
	if ($choice1 eq "4"){
		
	$c = 0;
	open (DEVICES, "cat /proc/net/wireless |");
	while (<DEVICES>){
		if ($c > "1"){
			$lin = $_;
			$linf = index($lin, ":");
			$length = $linf - "2";
			$device = substr($lin,"2", $length);
			@devices = ($device, @devices);
		}
	$c++
	}
	$numdev = $c - "2"; print "\nThere are $numdev wireless devices available\n";

	$d = 0;
	$e = $d + 1;
	while ($d < $numdev ){
#		$devicef{$e} = $devices[$d];
		print "$e - $devices[$d]\n";
		$d++;
		$e++;
	}

	print "Please select a device to use: ";
	chomp($devc = <STDIN>);
	$devc--;
	$dev1 = $devices[$devc];
	print "Device now in use is $dev1\n";	
	}


However, it does not seem this simple when using gtk-perl, because I need the variable name of each device set to the button before the program figures out how many available devices I have available. So I have ended up with the following code, where I have resorted to just writing the code out for each device that may exist (at this point only two, cause my laptop can only have two wireless devices at one time if I forget about usb devices...);

(relevant sections of code)

Code:
# search /proc/net/wireless for available devices

	$c = 0;
	open (DEVICES, "cat /proc/net/wireless |");
	while (<DEVICES>){
		if ($c > "1"){
			$lin = $_;
			$linf = index($lin, ":");
			$length = $linf - "2";
			$device = substr($lin,"2", $length);
			@devices = ($device, @devices);
		}
	$c++
	}

#number of devices available
	$numdev = $c - "2"; 

[\code]

The function of this code is obvious; then the @devices are used by the following code.

[code]

$table = new Gtk::Table( 1, 2, $true );

# Create first button
$button = new Gtk::Button( "$devices[0]" );
$button->signal_connect( "clicked", \&ButtonClicked, "$devices[0]" );

$table->attach_defaults( $button, 0, 1, 0, 1 );
$button->show();
# Create second button if device[1(2)] is available

if ($devices[1]){
$button = new Gtk::Button( "$devices[1]" );
$button->signal_connect( "clicked", \&ButtonClicked, "$devices[1]" );

$table->attach_defaults( $button, 1, 2, 0, 1 );
$button->show();
}

$frame = new Gtk::Frame( "Device Selection" );
$frame->add( $table );
$vbox->pack_start( $frame, $false, $false, 0 );


........................

sub ButtonClicked
{
   my ( $button, $text ) = @_;
   $dev1 = $text;
	
	&iwconfigget;		
	&ifconfigget;
   
   $labela->set_text( $dev1 );
}


.....................

sub iwconfigget {
	$count1 = 0;
	@iwca = ();
	open (IW, "/sbin/iwconfig $dev1 |");
	while (<IW>){
		@iwca = (@iwca, $_);
	}
	$count1 = $#iwca;
	$a = "0";
	$iwc = "";
	while ($count1 > $a){
		$iwc = $iwc.$iwca[$a];
		$a++;
	}
	$labele->set_text( $iwc );
}
########### iwconfig get end

## IFCONFIGGET sub
sub ifconfigget {
	$count2 = 0;
	@ifca = ();
	open (IW, "/sbin/ifconfig $dev1 |");
	while (<IW>){
		@ifca = (@ifca, $_);
	}
	$count2 = $#ifca;
	$a = "0";
	$ifc = "";
	while ($count2 > $a){
		$ifc = $ifc.$ifca[$a];
		$a++;
	}
	$labelf->set_text( $ifc );
}

I am interested by your comment "Some of it is style-related - there's an awful lot of c-style indexing variables and counts which need very rarely appear in perl code", as I am a relative noob to programming I dont see how using indexing variables can be avoided. Any suggestions welcome. If you want the whole code I can email it to you, it is quite long to put up here.
 
Thanks for being interested. Lets walk through some of your code.

Code:
# search /proc/net/wireless for available devices

    $c = 0;
    open (DEVICES, "cat /proc/net/wireless |");
    while (<DEVICES>){
        if ($c > "1"){
            $lin = $_;
            $linf = index($lin, ":");
            $length = $linf - "2";
            $device = substr($lin,"2", $length);
            @devices = ($device, @devices);
        }
    $c++
    }

#number of devices available
    $numdev = $c - "2";

Firstly, we don't need [tt]cat[/tt]. /proc/net/wireless behaves like a file so we can just open it and read it with
Code:
open (DEVICES, '/proc/net/wireless')
  or die "$0: /proc/net/wireless: $!";

You'll note that I've added a bit of error checking.

Secondly, [tt]@devices = ($device, @devices);[/tt] adds $device to the beginning of the @devices array. The [tt]push()[/tt] function does this much more efficiently. We simply say
Code:
push @devices, $device;

(We could have used [tt]unshift()[/tt] to add to the other end of the array. [tt]pop()[/tt] and [tt]shift()[/tt] remove items from either end).

Now we want to read this file and grab the device names into an array. The file contains something like
Code:
Inter-|sta|  Quality       |  Discarded packets
 face |tus|link level noise| nwid crypt  misc
  eth2: f0   15.  24.    4.   181     0     0

You're using [tt]$c[/tt] for two purposes - losing the first two lines ([tt]if ($c<1){[/tt]) and providing a count. The count is redundent: it's just the number of items in [tt]@devices[/tt]. Throwing away the first two lines would perhaps be more explicit with two null reads, as in
Code:
<DEVICES>;<DEVICES>; # discard two lines
but, as we'll see, there's a better way.

In the body of the loop, you're finding the first colon, subtracting two and extracting the required number of characters from the input string, all with associated variables. This is pretty much how you'd have to do it in, say, 'c' but Perl provides much cleaner text parsing idioms.

There are several neat ways to do this but I'm going to show a solution using regular expressions. They are perhaps slightly over-used in perl and there are sometimes more efficient solutions, but they are not inefficient in themselves and can produce some very concise, legible code.

We're interested in those input lines which have some optional leading spaces, followed by some word-characters, followed by a colon. In regex-speak, this is
Code:
/^\s*\w+:/

The ^ anchors the expression to the beginning of the line. The \s stands for any space character and the * modifier means none or more. The \w stands for any word character (A-Z, a-z, 0-9 or _) and the + modifier means one or more. The colon stands for itself. We can extract the word by parenthesising it - it is returned as $1 - like this
Code:
/^\s*(\w+):/

In fact, we don't even need $1 if we assign from the regex in array context. We can say
Code:
my($device) = /^\s*(\w+):/;

This assignment will fail if the input line does not match the pattern, so we can be very slick and say
Code:
while(<DEVICES>){
  next unless my($device) = /^\s*(\w+):/;
  push @devices, $device;
}

Using [tt]next[/tt] in a two-line loop should ring alarm bells, and I'd probably end up with the minimal (but still very readable)
Code:
while(<DEVICES>){
  push @devices, $1 if /^\s*(\w+):/;
}

We could now say
Code:
my $numdev = @devices;
but, as we're not using it, I'll omit that. It's rare to need the length of an array in perl as they tend to be processed using [tt]foreach[/tt], [tt]map[/tt] or [tt]grep[/tt] rather than using an explicit iterator. You may want something like
Code:
warn_no_connection() unless @devices;
to explicitly handle the case where there are no networks.

Now we've got our array of network names, we need to create the buttons. A [tt]foreach[/tt] loop is the natural way:
Code:
my $table = new Gtk::Table( 1, 2, $true );

foreach my $dev (@devices) {
  my $button = new Gtk::Button( $dev );
  $button->signal_connect( 'clicked', \&ButtonClicked, $dev );
  $table->attach_defaults( $button, 0, 1, 0, 1 );
  $button->show();
}

You'll note I've used lexical variables (introduced by [tt]my[/tt]) rather than globals. They only exist within the enclosing textual block so, in combination with [tt]use strict[/tt] (which will warn if you don't use lexicals), they will protect your variables from the rest of your code and vice versa. It's a habit well worth getting into which will save you hours of debugging in the future. Promise.

Somewhat pathologically, I've changed
Code:
"clicked"
to
Code:
'clicked'
. "" is an operator that constructs a new, anonymous, read-only string variable, possibly with interpolation. It parses it's contents looking for variables to interpolate. '' simply introduces a string constant, which is what we want here.

Here's the fragment.
Code:
my @devices;
while(<DEVICES>){
  push @devices, $1 if /^\s*(\w+):/;
}

my $table = new Gtk::Table( 1, 2, $true );

foreach my $dev (@devices) {
  my $button = new Gtk::Button( $dev );
  $button->signal_connect( 'clicked', \&ButtonClicked, $dev );
  $table->attach_defaults( $button, 0, 1, 0, 1 );
  $button->show();
}

I hope that this has been helpful. Let me know how you get on with the project.

Yours,

fish

[&quot;]As soon as we started programming, we found to our surprise that it wasn't as easy to get programs right as we had thought. Debugging had to be discovered. I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.[&quot;]
--Maur
 
Thanks fishiface for taking the time to walk through the code.

The first bit of code which you reduced from 12 lines to 4 was really interesting and has given me insight into some of the shortcuts possible. I have now applied the foreach and push functions on lots of parts of the rest of the code.

The one thing that did not work so well was the foreach function used on the buttons. For the button coordinates need to be different for each button. And, it seems the coordinates will not accept a variable (see code below), inplace of the numbers. I have reverted back to the original code I had for this bit, as after playing around for a while I could not get it working with the foreach loop.

I also thought that I had tried to put variables in button names before and it had not worked. Any suggestions?

Code:
my $table = new Gtk::Table( 1, 2, $true );

foreach my $dev (@devices) {
  my $a = 0;	my $b = 1;
  my $button = new Gtk::Button( $dev );
  $button->signal_connect( 'clicked', \&ButtonClicked, $dev );
  $table->attach_defaults( $button,$a, $b,0, 1 );
  $button->show();
  $a++; $b++;
}


Using the examples you intorduced me to above I was able to turn the following piece of code:

Code:
sub routeget {
	$count3 = 0;
	@ra = ();
	open (ROUTE, "/sbin/route -n |");
	while (<ROUTE>){
		@ra = (@ra, $_);
	}
	$count3 = $#ra;
	$count3++;
	$a = "0";
	$rout = "";
	while ($count3 > $a){
		$rout = $rout.$ra[$a];
		$a++;
	}
	$info = $rout;;
	print "info is $info\n";
	$labelr->set_text( $info );
}


into something a bit shorter like;

Code:
sub arpget {
	@arp = ();
	open (ARP, '/proc/net/arp') or die "0$: /proc/net/arp $!";
	while (<ARP>){
		push @arp, $_;
	}
	my $arpa; 
	foreach my $temp (@arp){
		$arpa = $arpa.$temp;
	}
	$info = $arpa;
	$labelr->set_text( $info ); ##set_text will not accept an array??
}

So thanks, that it is really appreciated!

Kind Regards,

TwinTriode
 
Your problem with
Code:
my $table = new Gtk::Table( 1, 2, $true );

foreach my $dev (@devices) {
  my $a[red] = 0[/red];    my $b [red]= 1[/red];
  my $button = new Gtk::Button( $dev );
  $button->signal_connect( 'clicked', \&ButtonClicked, $dev );
  $table->attach_defaults( $button,$a, $b,0, 1 );
  $button->show();
  $a++; $b++; [red]#useless[/red]
}

is that $a and $b are reset to 0 and 1 at the top of the loop - each time round. You should be fine with
Code:
my $table = new Gtk::Table( 1, 2, $true );

my $x = 0; my $y = 1;
foreach my $dev (@devices) {
  my $button = new Gtk::Button( $dev );
  $button->signal_connect( 'clicked', \&ButtonClicked, $dev );
  $table->attach_defaults( $button, $x++, $y++, 0, 1 );
  $button->show();
}
although incrementing both coordinates will result in an odd staircase effect. I'd expect to see only one incremented; $x for horizontal stacking and $y for vertical stacking.


Lets have a quick look at
Code:
sub arpget {
    @arp = ();
    open (ARP, '/proc/net/arp') or die "[red]0$[/red]: /proc/net/arp $!";
    while (<ARP>){
        push @arp, $_;
    }
    my $arpa; 
    foreach my $temp (@arp){
        $arpa = $arpa.$temp;
    }
    $info = $arpa;
    $labelr->set_text( $info ); ##set_text will not accept an array??
}

0$ is a typo for $0 - the current program name. I like to use this in error messages so that they make more sense in logfiles or popup-boxes.

Firstly, a possible problem. the [tt]while (<ARP>)[/tt] construct will return each line together with it's trailling newline character, which is probably not what you want. You could add a [tt]chomp()[/tt] in the loop to remove it but perl offers a prettier syntax for such a common task:
Code:
@arp = <ARP>;

You then use another loop to join the values together into a single string. Hmmm - perhaps this is why you left the newlines in. In either case, the join operator saves you a loop here. To join with no delimiters, simply specify a null delimiter:
Code:
my $arpa = join( '', @arp );

We can read without newlines and add them at the join in one, with
Code:
my $arpa = join( "\n", <ARP> );
which saves an intermediate array variable.

[tt]$info = $arpa;[/tt] is redundant because you could as easily use $arpa in the call to [tt]set_text()[/tt].

Although it's a mistake to keep condensing code beyond sensible readability limits, I'd be tempted by the two-liner
Code:
sub arpget {
    open (local ARP, '/proc/net/arp') or die "0$: /proc/net/arp $!";
    $labelr->set_text( join( "\n", <ARP> ) );
}

I've [tt]local[/tt]ised the filehandle so that it get's closed for us automatically when it goes out of scope (at the sub exit).

There's one problem with this that still troubles me, and that's the use of the global $labelr. I think that the code would be cleaner if you changed the interface to the sub and, accordingly, the way that you call it. Rather than
Code:
my $labelr = new GTK::Label; # or something?
arpget();

....

sub arpget {
    open (local ARP, '/proc/net/arp') or die "0$: /proc/net/arp $!";
    $labelr->set_text( join( "\n", <ARP> ) );
}
I thik that it would be much easier to understand if you wrote
Code:
my $labelr = new GTK::Label; # or something?
$labelr->set_text( arpget() );

....

sub arpget {
    open (local ARP, '/proc/net/arp') or die "0$: /proc/net/arp $!";
    return join( "\n", <ARP> );
}

This makes the use of calling arpget() immediately obvious without one having to look at it's definition. Keeping all operations on $labelr physically close and sequential aids legibility.

It also means that you can reuse this arpget() function in other code without rewriting it. It has become more general and hence more valuable.

Yours,

fish

[&quot;]As soon as we started programming, we found to our surprise that it wasn't as easy to get programs right as we had thought. Debugging had to be discovered. I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.[&quot;]
--Maur
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top