[MUSIC PLAYING]
So this video is going to focus on input and output in Perl.
First, we're going to cover standard input
and standard output, and then we're
going to follow that by learning how to read and write
from files in Perl.
It's really important that you already
understand arrays and strings, and basic Perl syntax
as well so that you can follow along.
First, we're going to start with standard input.
In Perl, we have access to anything entered
through standard input through a pre-created filehandle called
standard input.
So it's all capital letters, 'STDIN'.
How we use and access filehandles is we
surround them by angle brackets like this.
So for example, if I wanted to save all of the lines
from standard input, I want to put this into an array context.
So I might do that by creating an array called '@input'
and making it equal to the filehandle.
What that will do is take all the lines from standard input.
It splits them on the newline and saves each line
into that array.
So the length of the array will be the total number of lines
that we've gotten from standard input.
And now that I've got all the lines in an array,
we can just access them like normal in Perl.
So I can make a 'foreach' loop with my index,
go to whatever the length of the array is.
And what I'm going to do is I'm going
to print out the index for us so you can see how many elements
there are and where we're up to in the array,
followed by the line.
And what I'm going to do I'm going
to uppercase it so that we can see that there's
been a change made.
So I'm going to uppercase our line which is
going to be input at the index.
So now what I'm going to do, I'm going to run our program.
And I'm going to give it some input.
So 'this', 'is', 'some input'.
And now what I'm going to do to tell the program that
it's finished, I'm going to hit Control-D to say that
I've reached the end of entering input.
So I'm going to hit Control-D.
And now we can see we've got our output.
So we've got, at index 0, the line 'THIS';
at index 1, the line 'IS';
and at index 2 the line 'SOME INPUT',
which we can see-- each of the separate lines of input.
Now you'll notice I didn't actually
have to add a newline here to the end of my 'print' statement.
Normally we have to do that in Perl,
but we don't actually have to because each of these lines
here already has a newline at the end of it.
So I don't need to add one.
If I were to 'chomp' each of the lines like this,
then I'd need to add a newline.
But because I'm not doing that and they've already
got newlines on the end, I don't need to add one
in my 'print' statement.
So that's nice.
Now what I'm going to do is we're
going to do exactly the same thing, except I'm just
going to print the input just as a normal array using
a 'foreach' loop.
So if I just wanted to get the line for each
of the lines in our array like this--
so just get all the values from our array.
And then I'm going to print the lines uppercase like this.
So just like we would with a normal array,
if we just wanted to get all the values like this--
oops.
Oh, what have we got here?
Ah, got to change that to '$line'.
'./io.pl', 'this', 'is', 'some', 'input'.
All right, again, Control-D, and then we've
got each of the lines in our array.
So 'this is some input'.
Now, we could shortcut this by instead of saving it
as an array and then using a 'foreach' loop,
Perl is smart enough to know that if I'm
using a 'foreach' loop and if I put a filehandle in,
because it's in an array context what it will do
is it'll essentially treat it as if it's an array.
It'll take in all of the lines and then
it'll iterate it through as if it's an array
and print them one by one.
So now when I run it again, it runs exactly the same.
Control-D, yep, exactly the same as before except with one less
line, so that's nice.
But what if I don't want to go through all of the input
all at once?
What if I want to process things line by line?
It's often better to actually work through standard input
line by line instead of it having
to wait for all of standard input to come in, in which case
we use the filehandle in a scalar context.
For example, what I might do is let's
say I wanted to get line one.
I have a scalar variable, and then I'd use a filehandle.
That will give me the first line.
Then I could have line two.
That would give me the second line, et cetera, et cetera.
And I could go on for all the different lines of input.
But that would be a bit repetitive.
So, whenever I want to do something over and over again,
we would use a 'while' loop in Perl.
So instead of writing out all the individual lines,
I could say, 'while', '$line' is equal to standard input.
And then I can process each of the lines one at a time.
So I could do what we did before.
I could print the lines uppercase.
But in this case we're using it in a scalar context,
so it's just going to process each line one at a time.
You'll see the difference when I run the program.
So when I run our program and type 'this' and hit Enter,
you can see it processes the line immediately--
'is', and then 'some input', and then Control-D to exit.
So this means I can process each line as the program is
running instead of having to wait for the whole thing
at the end.
So, for example, what I can do now is if I wanted to make
an exception for a particular line--
let's say I wanted to not print out the first line--
I could call standard input here.
What that will do is it will essentially
save it to the default variable and nothing will happen.
And then what it'll do is it will
go through the rest of the lines of input
and print them uppercase.
So that way I can specify doing something special
for the first line.
So if I do this, run my program, 'this',
nothing happens.
'is', 'some input', yeah, there we go.
And that is how you use a filehandle in a scalar context.
Now, what if we wanted to decide to send it
to somewhere in particular?
Now 'print', by default, actually sends all our output
to standard output, which is pretty convenient for us.
But what if I wanted to specify that?
So what if I wanted to intentionally send something
to standard output?
'print' actually takes in a filehandle
as the first argument.
So I could use 'STDOUT', which is
a predefined filehandle for us.
And this will print to standard out, which would work exactly
the same as we've seen before.
So, I run the program--
'this is some input'--
and we can see it's printing to standard output as per normal.
So Control-D to finish.
So, similarly, if we wanted to print somewhere else-- let's
say I wanted to print to standard error.
There's a predefined standard-error variable, so
'STDERR'.
Now run the program again.
We know standard error should appear in the terminal.
So, 'this is some input'.
And just to show you that it's actually printing to
standard error, if I redirect standard error to somewhere else,
we can see that it doesn't print anything out.
And that's because we're printing to standard error.
And that is how you send something
to a particular filehandle.
So that is how you read from standard input and print
to standard output in Perl.
As we'll see next, having the ability to choose which
file stream 'print' will send the line to
will be really helpful for printing to other nonstandard
file streams--
for example, if you're writing to a file instead
of just standard output or standard error.
So this covers how to read from standard input
and write to standard output--
also standard error.
But what about files?
Well, similarly to C, we have two special functions
called 'open' and 'close'.
These functions help us to create our own filehandles
in Perl so that we can read and write to files.
So I have a file here called 'input.txt'
which has these lines:
'this input comes from a file called input.txt'.
And so I'm going to create a filehandle to open this file
and read from it, instead of reading from standard input.
Now, 'open' has three different arguments,
which my text editor, Sublime, will helpfully recommend.
So we've got these three arguments.
The first one is the filehandle.
The second one is the mode.
And the third one, the expression,
is essentially the file path.
So the first thing that you do is
we're going to create a new variable which
is our filehandle.
So I could create a variable like this for our filehandle.
And this method is often preferred, in which case
you'd access it by using the angle brackets like before,
using our filehandle like this.
Personally, I like to use the all-caps version like 'FILE'
or 'FILE_HANDLE' because it just visually reminds me
that I'm supposed to treat it like I would standard input
or standard error.
What we then do once we've got our filehandle,
the second argument is the mode.
Now there are different modes.
Typically you'll be using one of three--
reading, writing, and appending.
Now if you are familiar with 'bash',
you'll have seen these before.
For example, if I have a program called 'my_program'
and I want to give it input from somewhere else besides standard
input, I'd use the angle bracket like this.
And I'd, let's say, read from 'input.txt'.
Similarly, if I was to produce some output
and want to send it to a file, I might do this.
Or if I wanted to append to a file,
I would use the double arrow here.
And that is exactly the same syntax
that Perl uses for the mode.
So if I want it to read, I'll use the inner angle bracket.
And the last one, the expression, is,
where are we actually going to read the file from?
Now I have a file in the current directory called 'input.txt',
so I'll put that here.
Now remember, when you 'open' a file,
the next thing that you should always do is 'close' it.
So we'll use 'close' here.
And we just pass it to filehandle, which in this case
I've just called 'FILE'.
Now our program here will work exactly the same as before,
except now I'm going to change both of these variables to what
I want.
So instead of reading from standard input
I actually want to read from my file.
So I'm going to put in my filehandle instead.
And I don't want to print to standard error.
I want to print to standard output.
I could put nothing here.
That would work exactly the same,
but I'll leave that there for now.
So, if I run this program now, it'll behave exactly the same
as before except now it's going to be reading input from
the file instead of from the terminal.
So when I run it, it says 'this input comes
from a file called input.txt',
and it's capitalised all the lines and it's read them from
our file.
Now let's talk a little bit about safety.
So far we've just assumed that all of our files
have opened successfully, which isn't necessarily the case.
Hopefully-- well, thankfully--
well, hopefully they all work straight away,
but they might not.
So in Perl, we have this really helpful way
of running multiple commands if the first one fails.
So we can use 'or' in this special way
where we either do this thing or we can do that, just like this.
So in this case we want to either open the file
or we want to, essentially, fail gracefully.
Presumably our program won't work if we can't open the file,
so we've got to somehow fail in a way that tells the users
there's a problem that's gone wrong.
So what we want to do after our 'open' is we want it to open
'or' something else has to happen.
There's this function that we can
use in Perl called 'die' which does kind
of exactly what it sounds like.
It will kill the program if something fails.
So in this case it will either 'open' our file successfully
'or' it will 'die'.
So now when I run this it should work the first time
because 'input.txt' exists.
But if I change it to something else-- let's say 'dud_file.txt'--
when I run it again, it died.
And it told us that it died in this program 'io.pl' at line 3.
That's helpful information.
But what if we want to add some more helpful information
than that?
Well, what we can do is we can actually
pass an error message to 'die'.
So we're going to move this on the next line.
We can actually give die a string
which is our own error message.
So what I might do is save the file path in a variable
here so that we can use it in our error message.
So we might say something like "Cannot open file $file_path"
with a newline at the end.
Now when I run this broken program it gives this helpful
error message, "Cannot open dud_file.txt".
But we don't just have this as a helpful error message.
There's also something that we can use called '$!'.
Now I won't tell you what this does.
I'm just going to show you.
Ah, 'no such file or directory'.
In fact, it told you what the problem was.
So this hashbang has told you why the program died,
'no such file or directory', but it hasn't told you which
'no such file or directory'.
So usually you might do something like yes,
'cannot open $file_path', and then you put this there.
That way you know what it tried to open and failed to do.
So if I run this again, "Cannot open dud_file.txt--
no such file or directory."
And that is how you safely open files in Perl.
Now what we're going to do is we're
going to look at file output.
What if I want to output somewhere
besides just standard output?
Well, what we can do is we can actually
open a file for writing.
We'll do it similar to the syntax above.
So I'll copy this.
Maybe we'll do '$file_in' is our file 'input.txt'.
We might have '$file_out' equals 'output.txt'--
'$file_in', and same here.
And now what we're going to do is
we're going to open our file for writing.
So I'm going to change the mode to writing.
We're going to write to '$file_out'.
And we can also run into problems
when we're trying to open a file for writing.
If you're trying to open a file to write to it
and it's a directory it'll shout at you or if you've run out
of memory on your computer.
So you might say something like "Cannot create $file_out,"
something like that, or "Problem reading to $file_out."
And so we'll have an error message there too.
And just like before when we have our filehandle,
it's always good to close our filehandle.
Now the last change we have to do
once we've opened a file for reading
and opened our file for writing,
we're going to go through each of the lines in our file.
And instead of printing them to standard out,
I'm going to put my new filehandle here, 'FILE_OUT'.
This should take all the lines of input from 'input.txt'.
It should uppercase them and print them to 'output.txt'.
So now let's run this program.
Oh, looks like nothing happened.
But that's to be expected.
It's reading from a file and writing to a file.
Nothing's happening through the terminal.
So if I 'cat' our 'input.txt'--
remember that this input comes from a file called 'input.txt',
is our input.
And now if I look at our 'output.txt',
we've got exactly the same text except it's now all capitalised.
You can also create a file for appending.
So if I want to append to the file 'output.txt',
I'm going to run this program again.
And what we should see--
'cat input.txt' is exactly the same.
But when I 'cat output.txt',
you can see that extra input has been added to it,
and we've got the same thing twice.
In fact if I run it again, you can see it's there three times now,
whereas writing will actually overwrite whatever the file is.
So now we've only got the input once.
So those are the main modes that you'll
be using-- reading, writing, and appending.
But you can check out the Perl documentation
to see what other kinds are out there.
Cool.
So now we're done.
Now you know how to use input and output in Perl
with both standard input, standard output,
standard error, as well as opening
and creating your own filehandles to read and write files.
[MUSIC PLAYING]