I have this below code:
$cmd = system ("p4 change -o 3456789");
I want to print the output -description of the change list - into a file.
$cmd = system ("p4 change -o 3456789 > output_cl.txt");
will write the output in to output_cl.txt file.
But, is there anyway to get the output through $cmd?
open(OUTPUT, ">$output_cl.txt") || die "Wrong Filename";
print OUTPUT ("$cmd");
will write 0 or 1 to the file. How to get the output from $cmd?
To store the output of your p4 command into an array, use qx:
my #lines = qx(p4 change -o 3456789);
In addition to grabbing the entire output of a command with qx// or backticks, you can get a handle on a command's output. For example
open my $p4, "-|", "p4 change -o 3456789"
or die "$0: open p4: $!";
Now you can read $p4 a line at a time and possibly manipulate it as in
while (<$p4>) {
print OUTPUT lc($_); # no shouting please!
}
If you find it confusing remembering what you need to run in order to get a command's return value, vs. its output, or how to handle different return codes, or forget to right-shift the resulting code, you need IPC::System::Simple, which makes all this, well, simple:
use IPC::System::Simple qw(system systemx capture capturex);
my $change_num = 3456789;
my $output = capture(qw(p4 change -o), $change_num);
You can always use the following process to dump output straight to a file.
1) dup the system STDOUT file descriptor, 2) open STDOUT, 3) system, 4) copy the IO slot back into STDOUT:
open( my $save_stdout, '>&1' ); # dup the file
open( STDOUT, '>', '/path/to/output/glop' ); # open STDOUT
system( qw<cmd.exe /C dir> ); # system (on windows)
*::STDOUT = $save_stdout; # overwrite STDOUT{IO}
print "Back to STDOUT!"; # this should show up in output
But qx// is probably what you're looking for.
reference: perlopentut
Of course this could be generalized:
sub command_to_file {
my $arg = shift;
my ( $command, $rdir, $file ) = $arg =~ /(.*?)\s*(>{1,2})\s*([^>]+)$/;
unless ( $command ) {
$command = $arg;
$arg = shift;
( $rdir, $file ) = $arg =~ /\s*(>{1,2})\s*([^>]*)$/;
if ( !$rdir ) {
( $rdir, $file ) = ( '>', $arg );
}
elsif ( !$file ) {
$file = shift;
}
}
open( my $save_stdout, '>&1' );
open( STDOUT, $rdir, $file );
print $command, "\n\n";
system( split /\s+/, $command );
*::STDOUT = $save_stdout;
return;
}
Related
I am trying to create a couple of functions that will work together. getFH should take in the mode to open the file (either > or < ), and then the file itself (from the command line). It should do some checking to see if the file is okay to open, then open it, and return the file handle. doSomething should take in the file handle, and loop over the data and do whatever. However when the program lines to the while loop, I get the error:
readline() on unopened filehandle 1
What am I doing wrong here?
#! /usr/bin/perl
use warnings;
use strict;
use feature qw(say);
use Getopt::Long;
use Pod::Usage;
# command line param(s)
my $infile = '';
my $usage = "\n\n$0 [options] \n
Options
-infile Infile
-help Show this help message
\n";
# check flags
GetOptions(
'infile=s' => \$infile,
help => sub { pod2usage($usage) },
) or pod2usage(2);
my $inFH = getFh('<', $infile);
doSomething($inFH);
## Subroutines ##
## getFH ##
## #params:
## How to open file: '<' or '>'
## File to open
sub getFh {
my ($read_or_write, $file) = #_;
my $fh;
if ( ! defined $read_or_write ) {
die "Read or Write symbol not provided", $!;
}
if ( ! defined $file ) {
die "File not provided", $!;
}
unless ( -e -f -r -w $file ) {
die "File $file not suitable to use", $!;
}
unless ( open( $fh, $read_or_write, $file ) ) {
die "Cannot open $file",$!;
}
return($fh);
}
#Take in filehandle and do something with data
sub doSomething{
my $fh = #_;
while ( <$fh> ) {
say $_;
}
}
my $fh = #_;
This line does not mean what you think it means. It sets $fh to the number of items in #_ rather than the filehandle that is passed in - if you print the value of $fh, it will be 1 instead of a filehandle.
Use my $fh = shift, my $fh = $_[0], or my ($fh) = #_ instead.
As has been pointed out, my $fh = #_ will set $fh to 1, which is not a file handle. Use
my ($fh) = #_
instead to use list assignment
In addition
-e -f -r -w $file will not do what you want. You need
-e $file and -f $file and -r $file and -w $file
And you can make this more concise and efficient by using underscore _ in place of the file name, which will re-use the information fetched for the previous file test
-e $file and -f _ and -r _ and -w _
However, note that you will be rejecting a request if a file isn't writeable, which makes no sense if the request is to open a file for reading. Also, -f will return false if the file doesn't exist, so -e is superfluous
It is good to include $! in your die strings as it contains the reason for the failure, but your first two tests don't set this value up, and so should be just die "Read or Write symbol not provided"; etc.
In addition, die "Cannot open $file", $! should probably be
die qq{Cannot open "$file": $!}
to make it clear if the file name is empty, and to add some space between the message and the value of $!
The lines read from the file will have a newline character at the end, so there is no need for say. Simply print while <$fh> is fine
Perl variable names are conventionally snake_case, so get_fh and do_something is more usual
I am writing a Perl program to convert my local language ASCII characters to Unicode characters (Tamil).
This is my program
#!/bin/perl
use strict;
use warnings;
use open ':std';
use open ':encoding(UTF-8)';
use Encode qw( encode decode );
use Data::Dump qw(dump);
use Getopt::Long qw(GetOptions);
Getopt::Long::Configure qw(gnu_getopt);
my $font;
my %map;
GetOptions(
'font|f=s' => \$font,
'help|h' => \&usage,
) or die "Try $0 -h for help";
print "Do you want to map $font? (y/n)";
chomp( my $answer = lc <STDIN> );
$font = lc( $font );
$font =~ s/ /_/;
$font =~ s/(.*?)\.ttf/$1/;
if ( $answer eq "y" ) {
map_font();
}
else {
restore_map();
}
foreach ( #ARGV ) {
my $modfile = "$_";
$modfile =~ s/.*\/(.*)/uni$1/;
process_file( $_, $modfile );
}
sub process_file {
my #options = #_;
open my $source, '<', "$options[0]";
my $result = $options[1];
my $test = "./text";
my $missingchar = join( "|", map( quotemeta, sort { length $b <=> length $a } keys %map ) );
while ( <$source> ) {
$/ = undef;
s/h;/u;/g; #Might need change based on the tamil font
s/N(.)/$1N/g; #Might need change based on the tamil font
s/n(.)/$1n/g; #Might need change based on the font
s/($missingchar)/$map{$1}/g;
print "$_";
open my $final, '>:utf8', "$result";
print $final "$_";
close $final;
}
}
sub map_font {
my #oddhexes = qw/0B95 0B99 0B9A 0B9E 0B9F 0BA3 0BA4 0BA8 0BAA 0BAE 0BAF 0BB0 0BB2 0BB5 0BB3 0BB4 0BB1 0BA9/;
my #missingletters = qw/0BC1 0BC2/;
my #rest = qw/0B85 0B86 0B87 0B88 0B89 0B8A 0B8E 0B8F 0B90 0B92 0B93 0B83 0BBE 0BBF 0BC0 0BC6 0BC7 0BC8 0BCD 0B9C 0BB7 0BB8 0BB9 0BCB 0BCA 0BCC/;
foreach ( #oddhexes ) {
my $oddhex = $_;
$_ = encode( 'utf8', chr( hex( $_ ) ) );
print "Press the key for $_ :";
chomp( my $bole = <STDIN> );
if ( $bole eq "" ) {
next;
}
$map{$bole} = $_;
foreach ( #missingletters ) {
my $oddchar = encode( 'utf8', chr( hex( $oddhex ) ) . chr( hex( $_ ) ) );
print "Press the key for $oddchar :";
chomp( my $missingchar = <STDIN> );
if ( $missingchar eq "" ) {
next
}
$map{$missingchar} = $oddchar;
}
}
foreach ( #rest ) {
$_ = encode( 'utf8', chr( hex( $_ ) ) );
print "Press the key for $_ :";
chomp( my $misc = <STDIN> );
if ( $misc eq "" ) {
next
}
$map{$misc} = $_;
}
open my $OUTPUT, '>', $font || die "can't open file";
print $OUTPUT dump( \%map );
close $OUTPUT;
}
sub restore_map {
open my $in, '<', "$font" || die "can't open file: $!";
{
local $/;
%map = %{ eval <$in> };
}
close $in;
}
sub usage {
print "\nUsage: $0 [options] {file1.txt file2.txt..} \neg: $0 -f TamilBible.ttf chapter.txt\n\nOptions:\n -f --font - used to pass font name\n -h --help - Prints help\n\nManual mapping of font is essential for using this program\n";
exit;
}
In subroutine process_file, output of print "$_"; displays proper Tamil Unicode characters in the terminal.
However the output to the file handle $final is very different.
The %map is here.
Why are the outputs different?
How can I correct this behaviour?
I have seen this question but this is not the same. In my case the terminal displays the result correctly while the filehandle output is different.
Your open statement
open my $final, '>:utf8', "$result";
sets your file handle to expect characters, and to encode into UTF-8 sequences then on the way out. But you are sending it pre-encoded byte sequences from the %map hash, which causes those bytes to be treated as character and encoded again by Perl IO
In contrast, your terminal is set to expect UTF-8-encoded data, but STDOUT isn't set to do any encoding at all (use open ':std' has no effect on its own, see below) so it passes your UTF-8-encoded bytes through unchanged which happens to be what the terminal expects
By the way, you have set a default open mode of :encoding(UTF-8) for input and output streams with
use open ':encoding(UTF-8)'
but have overridden it in your call to open. The :utf8 mode does a very basic translation from wide characters to byte sequences, but :encoding(UTF-8) is far more useful because it checks that each character being printed is a valid Unicode value. There is a good chance that it would have caught a mistake like this, and it would have been better to allow the default and write just
open my $final, '>', $result;
To keep things clean and tidy, your program should work in characters, and the file handles should be set to encode those characters to UTF-8 when those characters are printed
You can set UTF-8 as the default encoding for all newly-opened file handles as well as STDIN and STDOUT by adding
use open qw/ :std :encoding(utf-8) /;
to the top of your program (:encoding(utf-8) is preferable to :utf8) and remove all calls to encode. You had it almost right, but the :std and :encoding(utf-8) need to be in the same use statement
You should also add
use utf8;
at the very top so that you can use UTF-8 characters in the program itself
You also have a few incidental errors. For instance
In the statement
open my $in, '<', "$font" || die "can't open file: $!";
it is almost always wrong to quote a single scalar variable like $font unless it happens to be an object and you want to invoke the stringification method
You need or instead of ||, otherwise you're just testing the truth of $font
If I asked you what a variable called $in might contain I think you'd be hesitant; $in_fh is better and is a common idiom
It's always nice to put the name of the file into the die string as well as the reason from $!
Taking all of those into account makes your statement look like this
open my $in_fh, '<', $font or die qq{Unable to open "$font" for input: $!};
You should be consistent between upper and lower case scalar variables, and lower case is the correct choice. So
open my $OUTPUT, '>', $font || die "can't open file";
should be something like
open my $out_fh, '>', $font or die qq{Unable to open "$font" for output: $!};
The line
$/ = undef;
should be local $/ as you have used elsewhere, otherwise you are permanently modifying the input record separator for the rest of your program and modules. It also appears after the first read from the file handle, so your program will read and process one line, and then the whole of the rest of the file in the next iteration of the while loop
I have a script that starts of with asking the user for a filename.
I want to add a feature so that i can supply a filename in the commandline even before running the program.
To the point:
If i started my program with "perl wordcounter.pl text.txt",
How could i access the string after the name of the program (ie text.txt), from within the code?
I started doing something like:
$filename = "Filename supplied in the commandline";
if ($filename == 0) {
print "Word frequancy counter.\nEnter the name of the file that you wish to analyze.\n";
chomp ($filename = <STDIN>);
}
Basically if no textfile was supplied at the commandline, $filename would still 0 and then it would proceed to ask for a file.
Anyone know how i can access the "Filename supplied in the commandline" in the code?
try using array #ARGV
$x1 = $ARGV[0] || 'NONE';
although #ARGV is a promising option but i would rather use getopts
here is the sample code that can be used according to your need
try this perl sample.pl -file text.txt and again without file argument perl sample.pl
#!/usr/bin/perl
use Getopt::Long;
# setup my defaults
GetOptions(
'file=s' => \$file,
'help!' => \$help,
) or die "Incorrect usage!\n";
if( $help ) {
print "Common on, it's really not that hard.\n";
} else {
print "reading the $name.\n";
if ( -e $file ){
open( DATA ,'<',$file ) or die "unable to open \$file = $file\n";
while (<DATA>){
print "$_\n";
}
} else {
chomp ($file = <STDIN>);
open( DATA ,'<',$file ) or die "unable to open \$file = $file\n";
while (<DATA>){
print "$_\n";
}
}
}
~
I made a file, "rootfile", that contains paths to certain files and the perl program mymd5.perl gets the md5sum for each file and prints it in a certain order. How do I redirect the output to a file if a name is given in the command line? For instance if I do
perl mymd5.perl md5file
then it will feed output to md5file. And if I just do
perl mydm5.perl
it will just print to the console.
This is my rootfile:
/usr/local/courses/cs3423/assign8/cmdscan.c
/usr/local/courses/cs3423/assign8/driver.c
/usr/local/courses/cs3423/assign1/xpostitplus-2.3-3.diff.gz
This is my program right now:
open($in, "rootfile") or die "Can't open rootfile: $!";
$flag = 0;
if ($ARGV[0]){
open($out,$ARGV[0]) or die "Can't open $ARGV[0]: $!";
$flag = 1;
}
if ($flag == 1) {
select $out;
}
while ($line = <$in>) {
$md5line = `md5sum $line`;
#md5arr = split(" ",$md5line);
if ($flag == 0) {
printf("%s\t%s\n",$md5arr[1],$md5arr[0]);
}
}
close($out);
If you don't give a FILEHANDLE to print or printf, the output will go to STDOUT (the console).
There are several way you can redirect the output of your print statements.
select $out; #everything you print after this line will go the file specified by the filehandle $out.
... #your print statements come here.
close $out; #close connection when done to avoid counfusing the rest of the program.
#or you can use the filehandle right after the print statement as in:
print $out "Hello World!\n";
You can print a filename influenced by the value in #ARGV as follows:
This will take the name of the file in $ARGV[0] and use it to name a new file, edit.$ARGV[0]
#!/usr/bin/perl
use warnings;
use strict;
my $file = $ARGV[0];
open my $input, '<', $file or die $!;
my $editedfile = "edit.$file";
open my $name_change, '>', $editedfile or die $!;
if ($input eq "md5file"){
while ($in){
# Do something...
print $name_change "$_\n";
}
}
Perhaps the following will be helpful:
use strict;
use warnings;
while (<>) {
my $md5line = `md5sum $_`;
my #md5arr = split( " ", $md5line );
printf( "%s\t%s\n", $md5arr[1], $md5arr[0] );
}
Usage: perl mydm5.pl rootfile [>md5file]
The last, optional parameter will direct output to the file md5file; if absent, the results are printed to the console.
Is there any way i can call a make utility through perl script.
I used the below code in myscript
$cmd=system("/...path../make");
print "$cmd";
but its not working
You can call any command you wish. It is typically done in backquotes for simplicity:
my $output = `make`;
print( $output );
Another common technique is to open a process for reading just like a file:
my $filehandle;
if ( ! open( $filehandle, "make |" ) ) {
die( "Failed to start process: $!" );
}
while ( defined( my $line = <$filehandle> ) ) {
print( $line );
}
close( $line );
The advantage of this is you can see the output as it is delivered from the process.
You may wish to capture STDERR output as well as STDOUT output by adding 2>&1 to the command line:
my $filehandle;
if ( ! open( $filehandle, "make 2>&1 |" ) ) {
die( "Failed to start process: $!" );
}
while ( defined( my $line = <$filehandle> ) ) {
print( $line );
}
close( $line );
You just need to use backquotes.
my $command = `make`;
print $command;
The return value of system is the exit status.
See here
EDIT: Link to system