1

I am new to Perl as I was trying to create a text file in a sub-directory named as data inside a directory named as a test, I have tried to write the following code, the code is as follows:-

#!/usr/bin/perl
use strict;
use warnings;
use Cwd qw(abs_path);
use File::Path qw(make_path remove_tree);
my $path = abs_path();
my @file = open(my $fh, '>>', '$path/test/data') or die "unable to create text file $!";
print $fh;
close $fh or die "unable to close file $fh $!\n";

It gave me the following error:-

unable to close file GLOB(0x1d5ea68) Bad file descriptor

Please it would be really helpful and appreciated if you can explain what is happening here? and how can it be solved? Thanks in advance.

lordadmira
  • 1,807
  • 5
  • 14
somilsharma
  • 49
  • 1
  • 8
  • 2
    Start with reading https://perldoc.perl.org/functions/print and also look up what `open` returns. – Shawn Nov 22 '20 at 18:00
  • 1
    Improper use of `open` and `print` - [open](https://perldoc.perl.org/functions/open), [print](https://perldoc.perl.org/functions/print) – Polar Bear Nov 22 '20 at 18:46
  • 1
    You don't need to use a module, or a fancy path to create a new file, just open `test/data` and it will automatically use a path relative to where the Perl program is. – TLP Nov 22 '20 at 18:49
  • 1
    @TLP - Not relative to the current directory? – Jim Davis Nov 22 '20 at 21:18
  • 1
    @JimDavis Those two will be the same unless you change directory. – TLP Nov 22 '20 at 22:46
  • 1
    @TLP - Or execute the script from another directory. – Jim Davis Nov 22 '20 at 23:56
  • 2
    @JimDavis Right, the correct phrase would be "relative to the current working directory". – TLP Nov 23 '20 at 16:47

3 Answers3

5

open failed, so $fh doesn't contain a valid file handle, and this is causing close to fail.


open is returning an error, but you aren't being notified about it because your check is incorrect. Because open always returns a value, the list assignment always returns 1, so the die could never be evaluated. (See Scalar vs List Assignment Operator if you want more details.)

Replace

my @file = open(...)
    or die "unable to create text file $!";

with

open(...)
    or die "unable to create text file $!";

As for why open is failing, the reason is surely that the path $path/test/data doesn't exist. You are literally looking for a directory named $path because single-quoted string literals don't interpolate.

Replace

'$path/test/data'

with

"$path/test/data"

or simply

'test/data'

since $path just contains the current directory.

ikegami
  • 367,544
  • 15
  • 269
  • 518
1

Not really an answer to your question, but I like to use the Path::Tiny module for file I/O. It's much easier to use than raw open calls, and you don't have to hunt for separate modules for most tasks.

#!/usr/bin/perl
use strict;
use warnings;
use Path::Tiny qw(path cwd);    # methods mkpath and remove_tree are available

my $file = cwd->child('test', 'data');
my $fh = $file->opena;  # dies on failure
print $fh;
close $fh or die "unable to close file $fh $!\n";

# But even easier to not bother with open/close and just do something like:
$file->append('...');
mscha
  • 6,509
  • 3
  • 24
  • 40
0

What that error is saying is that $fh is not an open file handle. The GLOB... part of the error message is the stringification of the file handle. Most likely $path/test/ doesn't exist so $fh is never opened. Open will not create directory components, only files. Check for it with -e first and create it with mkdir or make_path if necessary.

Your open or die clause doesn't work because you are capturing the open output into an array and that is what is evaluated by or.

I commend you for learning Perl to accomplish your task. Here is what you should be reading to solve these problems and continue on your path.

https://perldoc.pl/perl https://perldoc.pl/functions/open https://perldoc.pl/functions

HTH

lordadmira
  • 1,807
  • 5
  • 14
  • 1
    Re "*[an array] is evaluated by `or`*", Close, but no. `or`'s operand is a list assignment, not an array. A list assignment in scalar context returns the number of elements returned by its right-hand side, which is always one here. – ikegami Nov 23 '20 at 04:15
  • 1
    Re "*Most likely `$path/test/` doesn't exist*", So very true, but I don't think you noticed the OP is literally looking for a subdirectory named `$path`. The problem is the use of single-quotes. – ikegami Nov 23 '20 at 04:17
  • 1
    Here is the deparse. `((my(@file) = open(my $fh, '>>', '$path/test/data')) or die(('unable to create text file ' . $!)));` The `or` is checking `@file` in scalar context to decide whether to evaluate its RHS (die). `@file` contains the return value of `open` which is `undef`. You are right about the single quotes. – lordadmira Nov 23 '20 at 06:40
  • 1
    Re "*The or is checking @file in scalar context*", No, this is completely wrong. For starters, `@file` is evaluated in list context. And secondly, `=` is clearly `or`'s operand. The assignment has to be the last thing done before doing `or`. You can't do the assignment until you've evaluated `@file`. Would you claim that `f() + g()` returns `f()`? No. Well `@file = ...` doesn't return `@file`. – ikegami Nov 23 '20 at 14:45
  • 1
    Think of it like this: `or(lassign(@file, open))`. Your interpretation is clearly disproven with `() = open(...) or die;` According to you, that should always `die` because `()` in scalar context is false. But not, it never dies because a list assignment in scalar context returns the number of scalars returned by its RHS. See [Scalar vs List Assignment Operator](https://stackoverflow.com/a/54564429/589924) if you're still confused. – ikegami Nov 23 '20 at 14:46
  • 1
    Re "*Check for it with `-e` first and create it with `mkdir` or `make_path` if necessary.*" Bad advice!! Shouldn't use `-e` in that situation. See [here](https://stackoverflow.com/a/64960518/589924) for why and a better approach. Sorry, there are just too many problems with this answer. (Missing the actual problem, incorrect explanation of how assignment works, and bad suggestion to use `-e`.) – ikegami Nov 23 '20 at 14:50
  • 1
    You are technically correct that the `or` *opcode* takes the return value of the `aassign` *opcode* in order to short circuit. That is an implementation detail and doesn't change anything about my answer. `@file` will *always* contain whatever was returned by the RHS. Semantically, boolean `@file` is what is compared by `or` as the deparse clearly shows. The null list assignment is just an artifact of how list assignment was implemented; it's parser abuse. It should be illegal syntax IMHO. – lordadmira Nov 24 '20 at 06:45
  • 1
    99% of the time you do not have to check for file test race conditions. However if you want to check EEXIST that is very laudable. – lordadmira Nov 24 '20 at 06:50
  • 1
    100% of the time, you don't have to do extra work for nothing. mkdir already does an existence check. Doing a second one is pointless – ikegami Nov 24 '20 at 06:55
  • 1
    @ikegami I didn't know that! – lordadmira Nov 24 '20 at 07:00