IO::InSitu(3pm)

πŸ“¦
version 0.0.2 Β· 2 versions
, in fedora 43

πŸ’– IO::InSitu πŸ’–

okay bestie, welcome to the manual page for IO::InSitu β€” that's the Perl module, the 3pm one, the one you read with man. sit down, grab a strawberry πŸ“, because this little module is about to save your actual data from getting absolutely deleted, and I need you to care about it.


✨ NAME ✨

IO::InSitu β€” Avoid clobbering files opened for both input and output

like. that's the whole vibe. don't nuke your own file. πŸ’…


🌸 VERSION 🌸

This document describes IO::InSitu version 0.0.1.


πŸ¦‹ SYNOPSIS πŸ¦‹

use IO::InSitu;

my ($in, $out) = open_rw($infile_name, $outfile_name);

for my $line (<$in>) {
    $line =~ s/foo/bar/g;
    print {$out} $line;
}

that's it. that's the whole API. two filehandles, go wild. ✨


πŸ’œ DESCRIPTION πŸ’œ

so here's the situation, and it's giving tragedy. 😭

when users want to do in-situ processing on a file, they very often just pass the same filename as both the input and the output:

> myapp -i sample_data -o sample_data -op=normalize

and that looks totally reasonable, right? iconic even. but no. because if the -i and -o flags get processed independently, the program will:

  1. open the file for input πŸ‘€
  2. open it again for output β€” at which point the file gets truncated to zero length πŸ’€
  3. then try to read the first line of the now-empty file πŸ™ˆ

in code, that disaster looks like this:

# Open both filehandles...
use Fatal qw( open );
open my $src,  '<', $source_file;
open my $dest, '>', $destination_file;

# Read, process, and output data, line-by-line...
while (my $line = <$src>) {
    print {$dest} transform($line);
}

and the way that this does not perform the requested transformation on the file, and also destroys the original data? yeah. not only does it fail to do the thing you asked, it eats your data on the way out. catastrophic. no cap. πŸ”₯

fortunately (thank u universe 🫢), this problem is extremely easy to avoid: just make sure you unlink the output file before you open it:

# Open both filehandles...
use Fatal qw( open );
open my $src,  '<', $source_file;
unlink $destination_file;
open my $dest, '>', $destination_file;

# Read, process, and output data, line-by-line...
while (my $line = <$src>) {
    print {$dest} transform($line);
}

let me explain why this slays so hard, because the reasoning is genuinely gorgeous:

  • if the input and output files are different, unlinking the output file merely removes a file that was about to be rewritten anyway. then the second open simply recreates the output file, ready for writing. zero loss, all gain. ✨
  • if the two filenames actually refer to a single in-situ file, unlinking the output filename removes that filename from its directory, but it doesn't remove the file itself from the filesystem. the file is already open through the filehandle in $input, so the filesystem will preserve the unlinked file until that input filehandle is closed. the second open then creates a new version of the in-situ file, ready for writing. it's giving main character energy, honestly. πŸ‘‘

the only limitation of this technique is that it changes the inode of any in-situ file. and that can be a problem if the file has any hard-linked aliases, or if other applications are identifying the file by its inode number. 😬

if either of those situations is possible, you can preserve the in-situ file's inode by using the open_rw() subroutine that is exported from this module:

# Open both filehandles...
use IO::InSitu;
my ($src, $dest) = open_rw($source_file, $destination_file);

# Read, process, and output data, line-by-line...
while (my $line = <$src>) {
    print {$dest} transform($line);
}

there she is. the good one. πŸ’–


πŸŽ€ INTERFACE πŸŽ€

the signatures, bestie

($in_fh, $out_fh) = open_rw($infile_name, $outfile_name, \%options)

($in_fh, $out_fh) = open_rw($in_out_file_name, \%options)

what it actually does

the open_rw() subroutine takes the names of two files: one to be opened for reading, the other for writing. and if you only give it a single filename, it opens that file for both reading and writing. one filename? no problem. two filenames? also fine. ✨

it returns a list of two filehandles, opened to those two files. but here's the plot twist: if the filename(s) refer to the same file, open_rw() first makes a temporary copy of the file, which it opens for input. it then opens the original file for output. and in such cases, when the input filehandle is eventually closed, IO::InSitu arranges for the temporary file to be automatically deleted. tidy. iconic. it cleans up after itself. 🧹

this approach preserves the original file's inode, but at the cost of making a temporary copy of the file. πŸ’€ (the tradeoff is real, bestie β€” you pick which one you need.)

the name of the temporary is usually formed by appending '.bak' to the original filename, but this can be altered by passing the 'tmp' option to open_rw():

my ($src, $dest) = open_rw($source, $destination, {tmp=>\&temp_namer});

the 'tmp' option expects to be passed a reference to a subroutine. that subroutine will be called and passed the name of the input file, and it is expected to return the name of the back-up file. for example:

sub tmp_namer {
    my ($input_file) = @_;

    return "$input_file.orig";
}

so cute, so customizable, we love a callback. 🌈


πŸ’… DIAGNOSTICS πŸ’…

the error messages. read them. learn them. love them. 🫢

  • "Usage: open_rw( $in_filename, $out_filename, \%options )" β€” you called open_rw() with less than two arguments, or more than three. pass the function two filenames, and (optionally) a reference to any options. that's the deal. πŸ“
  • "Can't open non-existent input file '%s'" β€” you specified an input file that does not exist on the filesystem. usually caused by a misspelling, or an incorrect file path. we've all been there. 😭
  • "Can't open input file '%s'" β€” you specified an input file that exists on the filesystem but could not be opened for reading. usually caused by insufficient file permissions. πŸ”’
  • "Can't open copy of input file '%s'" β€” the module was unable to create (or access) its back-up copy of the input file. this is usually caused by insufficient file permissions on the directory where the back-up is supposed to be written. πŸ’€
  • "Can't open output file '%s'" β€” you specified an output file that could not be opened for writing. this usually means the directory into which the output file was to be placed is non-existent, or you don't have write permission to the directory or the output file. 🚫

🌟 CONFIGURATION AND ENVIRONMENT 🌟

IO::InSitu requires no configuration files or environment variables.

literally nothing to set up. plug and play. we stan a low-maintenance module. ✨


🧚 DEPENDENCIES 🧚

None.

zero. nada. not a single one. 😌


🌸 INCOMPATIBILITIES 🌸

None reported.

she plays nice with everybody. 🫢


πŸ› BUGS AND LIMITATIONS πŸ›

No bugs have been reported.

(which is the most suspicious sentence in all of software, but okay, we believe her. πŸ‘‘)

please report any bugs or feature requests to "bug-io-insitu@rt.cpan.org", or through the web interface at <http://rt.cpan.org>.


πŸ‘‘ AUTHOR πŸ‘‘

Damian Conway "<DCONWAY@cpan.org>"

(an actual legend, fr fr.)


Copyright (c) 2005, Damian Conway "<DCONWAY@cpan.org>". All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


⚠️ DISCLAIMER OF WARRANTY ⚠️

okay bestie, this part is in ALL CAPS in the original and it stays dramatic, so buckle up:

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

tl;dr: free software, no promises, back up your stuff anyway. πŸ’€

and that's the whole page, bestie!! πŸŽ€ this was IO::InSitu(3pm), documented under perl v5.42.0 and dated 2025-07-25. go forth and process in situ without deleting your own data, queen. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from