I've written a Bash script called `clock`, which is called by one of
two symbolic links: `clock-in` and `clock-out`. This script is,
effectively, a time-clock for tracking time spent on various projects
for clients. This script generates a "time card" file, if one does
not exist in my "$HOME/Timecards" folder, and/or appends to the "time
card' file. The file name is the first parameter passed into my
`clock` script. The second parameter is a note to be placed with
either the clock-in or clock-out to give a little detail to the "time
card".
Now, I'm trying to write a Perl script that will open a "time card"
and process it to generate an invoice based on the information in the
"time card" file. However, whenever I try to open any of the time
card files in my Timecards folder, I get the error "Inappropriate
ioctl for device". I've read other posts in this group, as well as
many others, but have not found a solution to this error. I am
pasting the code below for your review, to see if I've made some kind
of odd mistake with the Perl syntax. I've got a lot of programming
experience, but not much with Perl, so I'm just applying my knowledge
of other languages to Perl and trying to learn the syntax as I go.
Please forgive any conventions that I may have used that aren't common
to Perl. Here's the code in question:
######################################################################
# Create a variable to hold the path to the TimeCard files. These #
# should be located in a folder, under the user's home folder, called#
# 'Timecards'. #
######################################################################
$TimeCardsPath = $ENV{'HOME'} . "/Timecards/";
#######################################################################
# Check for command-line arguments. If there are none, call exit()
#
# with the command-line usage error as a parameter.
#
#######################################################################
if ( !defined( $ARGV[0] ) ) {
$ExitStatus = $EX_USAGE;
ExitScript();
} else {
# Let the user know that we are processing the filename that they
passed
# in to the script. Also, this shows the user that we are processing
# TimeCards from a specific directory in their $HOME folder,
according
# to the Bash Environment Variable.
print "Processing: $TimeCardsPath$ARGV[0]\n";
# Declare a variable to hole each line of the TimeCard file.
$Line = "";
# Declare four variables to hold the introductory note, time in, time
out
# and closing note of the clock punch.
$iNote = "";
$TimeIn = "";
$TimeOut = "";
$cNote = "";
# Now, we need to create a file handle to access the TimeCard file.
open ( TIMECARD, $TimeCardsPath . $ARGV[0] ) ||
die $ExitStatus = $EX_NOINPUT;
# Check to see if we were able to open the TimeCard file:
if ( $ExitStatus = $EX_NOINPUT ) {
# We need to let the user know that we were unable to open
# the TimeCard and exit the script.
print "Unable to open the file:\n";
print "\t$TimeCardsPath$ARGV[0]\n";
print "Please make sure that the file exists in the above path\n";
print "and that you have read access to the file.\n";
print "\nReturned Error:\n\t";
print "$!\n\n";
ExitScript();
} else { # Process the file and create the invoice.
# Implement processing here.
# Close the input file.
close ( TIMECARD );
# Exit the script with normal termination status.
exit ( $EX_OK );
} # End of invoice generation.
} # End of command-line parameter check.
sub ExitScript {
if ( $ExitStatus == 64 ) {
system ( 'clear' );
print "INVOICE GENERATOR v0.6\n";
print "~~~~~~~~~~~~~~~~~~~~~~\n";
print "PekinSOFT Systems\n";
print "Copyright(c) 2008\n";
print "\n";
print "\n";
print "USAGE: invoice {TimeCard Filename}\n";
print "\n";
print " TimeCard Filename\tName of the time card for which to have
an invoice generated.\n";
print "\n";
} else {
exit( $ExitStatus );
} # End of ExitStatus check.
} # End of ExitScript() function.
This code includes everything except my opening comments describing
the script and the definition of my exit status codes, which are taken
directly from `sysexits.h`. In my `die` statement, I'm setting the
ExitStatus variable, which is not something that I see in examples of
the `open` statement...I only see something like: `die "Error
message"`, so I don't know if that's causing the problem or even if
that is a legal statement.
Here is the output that I'm getting when I run the script:
Processing: /home/sean/Timecards/TimeCards
Unable to open the file:
/home/sean/Timecards/TimeCards
Please make sure that the file exists in the above path
and that you have read access to the file.
Returned Error:
Inappropriate ioctl for device
As you can see in the output, the path is displaying correctly, so I
assume that the path is also correct in the `open` statement.
However, in the `open` statement, I am using the concatenation
operator (.) instead of doing like it is in my `print` statements.
Could this be causing the problem?
Steps that I've taken:
1) Checked to make sure that I have no typos in my path and I don't.
2) Checked to make sure that the file exists and it does.
3) Checked to make sure that I have read access to the file and the
Timecards folder, which I do because I
created the folder and the file through my Bash script.
4) Made sure that I have no errors on my file system and I don't.
Any and all help is much appreciated. My system is as follows, in
case this information is useful to you to help me diagnose this error.
Dell Dimension 3000
Intel Pentium IV 3.0 GHz
1 GB DDR-RAM
70 GB HDD
nVidia GE Force FX-5500, 128 MB VRAM
Slackware 12.0
`uname -a`: Linux {HOSTNAME} 2.6.21.5-smp #2 SMP Tue Jun 19 14:58:11
CDT 2007 i686 Intel(R) Pentium(R) 4 CPU 3.00GHz GenuineIntel GNU/Linux
KDE 3.5.7
XOrg Server 1.4.0.90
Thank you very much for any and all assistance you are able to provide
to help me figure this one out. Thankfully, this is just for me and
not for a client.
Cheers,
Sean Carrick
PekinSOFT Systems
PekinSOFT at gmail dot com
http://www.pekinsoft.net/
Hello,
That is usually written as:
if ( @ARGV != 1 ) {
> $ExitStatus = $EX_USAGE;
> ExitScript();
Why not pass $EX_USAGE to ExitScript() instead of using global $ExitStatus?
ExitScript( $EX_USAGE );
> } else {
> # Let the user know that we are processing the filename that they
> passed
> # in to the script. Also, this shows the user that we are processing
> # TimeCards from a specific directory in their $HOME folder,
> according
> # to the Bash Environment Variable.
> print "Processing: $TimeCardsPath$ARGV[0]\n";
>
> # Declare a variable to hole each line of the TimeCard file.
> $Line = "";
>
> # Declare four variables to hold the introductory note, time in, time
> out
> # and closing note of the clock punch.
> $iNote = "";
> $TimeIn = "";
> $TimeOut = "";
> $cNote = "";
>
> # Now, we need to create a file handle to access the TimeCard file.
> open ( TIMECARD, $TimeCardsPath . $ARGV[0] ) ||
> die $ExitStatus = $EX_NOINPUT;
die() exits the program.
> # Check to see if we were able to open the TimeCard file:
> if ( $ExitStatus = $EX_NOINPUT ) {
You are using the assignment operator so the test is always true. You
need to test for numerical equality instead:
if ( $ExitStatus == $EX_NOINPUT ) {
> # We need to let the user know that we were unable to open
> # the TimeCard and exit the script.
> print "Unable to open the file:\n";
> print "\t$TimeCardsPath$ARGV[0]\n";
> print "Please make sure that the file exists in the above path\n";
> print "and that you have read access to the file.\n";
> print "\nReturned Error:\n\t";
> print "$!\n\n";
You are using the $! variable six statements away from the open()
statement which means that there is no guarantee that its value will be
related to what open() may have set it to.
> ExitScript();
> } else { # Process the file and create the invoice.
> # Implement processing here.
>
>
> # Close the input file.
> close ( TIMECARD );
> # Exit the script with normal termination status.
> exit ( $EX_OK );
> } # End of invoice generation.
> } # End of command-line parameter check.
>
> sub ExitScript {
Pass the exit code to your subroutine:
my $ExitStatus = shift;
> if ( $ExitStatus == 64 ) {
Shouldn't that be:
if ( $ExitStatus == $EX_USAGE ) {
> system ( 'clear' );
> print "INVOICE GENERATOR v0.6\n";
> print "~~~~~~~~~~~~~~~~~~~~~~\n";
> print "PekinSOFT Systems\n";
> print "Copyright(c) 2008\n";
> print "\n";
> print "\n";
> print "USAGE: invoice {TimeCard Filename}\n";
> print "\n";
> print " TimeCard Filename\tName of the time card for which to have
> an invoice generated.\n";
> print "\n";
The subroutine is named 'ExitScript' but you are not exiting here?
John
--
Perl isn't a toolbox, but a small machine shop where you
can special-order certain sorts of tools at low cost and
in short order. -- Larry Wall
> Why not pass $EX_USAGE to ExitScript() instead of using global $ExitStatus?
>
> ExitScript( $EX_USAGE );
Consistency is all. I try to get into a habit of doing things the
same way all of the time. Also, once I get the program debugged, I'll
end up with only one call to ExitScript somewhere at the bottom of the
script. I've only placed multiple calls to ExitStatus for debugging
purposes, though I don't know why.
>
> die() exits the program.
>
Yes, I understand that die() exits the program. My question was are
you able to process more than one line of code in a die() context?
Since I didn't know the answer to that, I set my $ExitStatus varialbe
to $EX_NOINPUT so that I would be able to process more than one line
of code before `die`ing.
>
> You are using the assignment operator so the test is always true. You
> need to test for numerical equality instead:
>
> if ( $ExitStatus == $EX_NOINPUT ) {
>
There's my problem. Thank you for pointing it out to me. I must have
looked at that line of code 100 times and never noticed that I
assigned instead of compared. I corrected that in my script and it
works fine now. Now, I am able to move on to actually processing the
file.
>
You are using the $! variable six statements away from the open()
> statement which means that there is no guarantee that its value will be
> related to what open() may have set it to.
>
Yes, but I'm doing nothing between the open() and where I use it that
should cause it to be changed. Plus, if you look at the output from
the run of my script, you can see that it is reporting the correct
error. However, you bring up a good point regarding the distance, so
I am going to make the next line after the open() statement an
assignment to a variable to hold that exit error, just in case.
>
>
> Pass the exit code to your subroutine:
>
> my $ExitStatus = shift;
>
What does `shift` do? And, how does the line `my $ExitStatus = shift;`
pass the exit code to my subroutine?
>
> > if ( $ExitStatus == 64 ) {
>
> Shouldn't that be:
>
> if ( $ExitStatus == $EX_USAGE ) {
>
Yes, yes it should...thanks for noticing that one. *grin*
>
> > system ( 'clear' );
> > print "INVOICE GENERATOR v0.6\n";
> > print "~~~~~~~~~~~~~~~~~~~~~~\n";
> > print "PekinSOFT Systems\n";
> > print "Copyright(c) 2008\n";
> > print "\n";
> > print "\n";
> > print "USAGE: invoice {TimeCard Filename}\n";
> > print "\n";
> > print " TimeCard Filename\tName of the time card for which to have
> > an invoice generated.\n";
> > print "\n";
>
> The subroutine is named 'ExitScript' but you are not exiting here?
>
Good catch! My logic is flawed here. The exit($ExitStatus) call
should not be in and `else` block, there shouldn't *be* and `else`
block. I've got that fixed.
>
> John
>
Thank you for all of your input, John. I've made the corrections
noted above and the script now seems to be easier to read and running
the way I intended. All-in-all, though, that's not too bad for my
first Perl script, huh? I mean, I normally develop in Java and Qt,
but just wanted a quick and dirty script to parse my TimeCard files
into invoices. However, I didn't want to use Bash because that would
have involved a lot of `grep`ing, `awk`ing and `sed`ing, and I'm not
too good with awk and sed.
die(), like print() and warn(), prints a list of strings. To do what I
think you are trying to do:
open my $FH, '<', 'somefile' or do {
# some statement here;
# and another statement;
# and finally;
die "some string here";
};
> Since I didn't know the answer to that, I set my $ExitStatus varialbe
> to $EX_NOINPUT so that I would be able to process more than one line
> of code before `die`ing.
>
> [ SNIP ]
>
>> You are using the $! variable six statements away from the open()
>> statement which means that there is no guarantee that its value will be
>> related to what open() may have set it to.
>>
> Yes, but I'm doing nothing between the open() and where I use it that
> should cause it to be changed.
You have print() six times, any one of which could change the value of $!.
> Plus, if you look at the output from
> the run of my script, you can see that it is reporting the correct
> error. However, you bring up a good point regarding the distance, so
> I am going to make the next line after the open() statement an
> assignment to a variable to hold that exit error, just in case.
>>
>> Pass the exit code to your subroutine:
>>
>> my $ExitStatus = shift;
>>
> What does `shift` do?
With no arguments, in file scope it removes an element from the
beginning of the @ARGV array, in subroutine scope it removes an element
from the beginning of the @_ array. The @_ array holds the list of
scalars that was passed to the subroutine.
perldoc -f shift
perldoc perlsub
> And, how does the line `my $ExitStatus = shift;`
> pass the exit code to my subroutine?
If you call the subroutine with the exit code as the first argument, for
example:
ExitScript( $EX_USAGE );
There are at least three ways to do this. The way above is good if
this is something that needs to happen only on this error. If you
always want a piece of code to run when an error occurs you can
override the __DIE__ signal:
$SIG{__DIE__} = sub {
#do stuff like write to a log file
}
And if you need something to always run at the end of your program
(regardless if there was an error or not), you can use an END block:
END {
print "This will always run at the end of the program, regardless
of die() or exit()\n";
}
--
Chas. Owens
wonkden.net
The most important skill a programmer can have is the ability to read.