Another way to use math expressions in shell scripts

Today I’d like to expand a bit more on the basic calculations in Unix scripts.

Use Parenthesis to simplify math expressions

In Basic Arithmetic Operations post I’ve shown you how expression evaluation can be used to calculate simple math expressions in your Unix shell:

ubuntu$ START=1
ubuntu$ FINISH=10
ubuntu$ ELAPSED=$(($FINISH - $START))
ubuntu$ echo $ELAPSED
9

Although this approach looks clean enough, there’s a way to simplify it even further if you put everything in parenthesis. In other words, the same result (ELAPSED receiving a correct value of FINISH value minus START value) can be achieved this way:

ubuntu$ ((ELAPSED=FINISH-START))
ubuntu$ echo $ELAPSED
9

It’s a matter of preference, and I used to always do such calculations the former way shown, but now that one of the readers of this blog pointed out the latter way of using it, I think I might change my habits – I actually like this way much better.

See also:




Updating Values of Your Shell Variables in Unix

If you’re just getting started with your Unix scripting or new to Unix shell, today’s little tip will make your life much easier: I’m going to show you how to update your shell variables.

Updating shell variable in Unix

If you have a text variable in your script and would like to append some text to it or somehow process this value, it’s perfectly normal to reuse the current variable value using a syntax like this:

ubuntu$ echo $VAR1
hello
ubuntu$ VAR1=$VAR1" world!"
ubuntu$ echo $VAR1
hello world!

You see? It’s this easy!

Common cases of reusing variable values in Unix shells

Most frequently, I use the technique described above to update some of my environment variables. I’m sure you’ll find them useful too.

Updating the PATH variable

PATH is the user environment variable responsible for the directories where Unix shell looks for executable commands every time you type something. Quite often you get a “file not found” error not because there isn’t such a command installed in your OS, but simply because your PATH variable has not a corrept path to a directory with that command.

Here’s an example from a standard user on one of my Red Hat Enterprise Linux systems.

I like the runlevel command, it’s quite simple and can be useful in scripts. When I attempt to run it, here’s what happens:

redhat$ runlevel
bash: runlevel: command not found

Like I said earlier, it’s most likely because my PATH variable doesn’t have the /sbin directory which is where this command resides. Let’s confirm this:

redhat$ echo $PATH
/usr/lib/qt-3.3/bin:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin

Now it’s time to update the PATH and append the /sbin path to it:

redhat$ PATH=$PATH:/sbin

runlevel command will be found now and here’s a proof:

redhat$ runlevel
N 5

Updating the MANPATH variable

Another good example is the MANPATH variable, which contains the list of directories with manpages which man command uses.

If some command doesn’t have its man page in neither of the directories specified by MANPATH, you’ll get an error.

Here’s an example from one of the Solaris systems, I’m looking for a Veritas vxprint command man page:

solaris$ man vxprint
No manual entry for vxprint.
solaris$ echo $MANPATH
/usr/man:/opt/sfw/man:/usr/perl5/man

Let’s add the /opt/VRTS/man to the MANPATH variable:

bash-2.03# MANPATH=$MANPATH:/opt/VRTS/man
solaris$ echo $MANPATH
/usr/man:/opt/sfw/man:/usr/perl5/man:/opt/VRTS/man

It’s bound to work now:

solaris$ man vxprint
Reformatting page.  Please Wait... done
...

That’s all I wanted to share with you today. Hope you liked the tip, and as always – feel free to ask any questions!

See also




How To Parse Text Files Line by Line in Unix scripts

I’m finally back from my holidays and thrilled to be sharing next of my Unix tips with you!

Today I’d like to talk about parsing text files in Unix shell scripts. This is one of the really popular areas of scripting, and there’s a few quite typical limitations which everyone comes across.

Reading text files in Unix shell

If we agree that by “reading a text file” we assume a procedure of going through all the lines found in a clear text file with a view to somehow process the data, then cat command would be the simplest demonstration of such procedure:

redhat$ cat /etc/redhat-release
Red Hat Enterprise Linux Client release 5 (Tikanga)

As you can see, there’s only one line in the /etc/redhat-release file, and we see what this line is.

But if you for whatever reason wanted to read this file from a script and assign the whole release information line to a Unix variable, using cat output would not work as expected:

bash-3.1$ for i in `cat /etc/redhat-release`; do echo $i; done;
RedHat
Enterprise
Linux
Client
release
5
(Tikanga)

Instead of reading a line of text from the file, our one-liner splits the line and outputs every word on a separate line of the output. This happens because of the shell syntax parsing – Unix shells assume space to be a delimiter of various elements in a list, so when you do a for loop, Unix shell interpreter treats each line with spaces as a list of elements, splits it and returns elements one by one.

How to read text files line by line

Here’s what I decided: if I can’t make Unix shell ignore the spaces between words of each line of text, I’ll disguise these spaces. Since my solution was getting pretty bulky for a one-liner, I’ve made it into a script. Here it is:

bash-3.1$ cat /tmp/cat.sh
#!/bin/sh
FILE=$1
UNIQUE='-={GR}=-'
#
if [ -z "$FILE" ]; then
        exit;
fi;
#
for LINE in `sed "s/ /$UNIQUE/g" $FILE`; do
        LINE=`echo $LINE | sed "s/$UNIQUE/ /g"`;
        echo $LINE;
done;

As you can see, I’ve introduced an idea of a UNIQUE variable, something containing a unique combination of characters which I can use to replace spaces in the original string. This variable needs to be a unique combination in a context of your text files, because later we turn the string back into its original version, replacing all the instances of $UNIQUE text with plain spaces.

Since most of the needs of mine required such functionality for a relatively small text files, this rather expensive (in terms of CPU cycles) approach proved to be quite usable and pretty fast.

Update: please see comments to this post for a much better approach to the same problem. Thanks again, Nails!

Here’s how my script would work on the already known /etc/redhat-release file:

bash-3.1$ /tmp/cat.sh /etc/redhat-release
Red Hat Enterprise Linux Client release 5 (Tikanga)

Exactly what I wanted! Hopefully this little trick will save some of your time as well. Let me know if you like it or know an even better one yourself!

Related books

If you want to learn more, here’s a great book:


classic-shell-scripting
Classic Shell Scripting

See also:




Unix scripts: basic arithmetic operations

As I was writing the Time and date in Unix scripts post last week, I’ve realized that there’s one really useful feature of Unix shell scripting which I haven’t covered on my pages here: it’s the double bracket parenthesis which allows you to evaluate arithmetic expressions.

Basic arithmetic calculations in Unix scripts

In most scenarios, your script will need only basic arithmetic operations: summing or subtracting numbers, multiplying or dividing them. Using only these operations, you’ll be able to implement all sorts of counters and calculate percentages or any other progress indicators you may need.

So far, I’ve only shown you this way of evaluating arithmetic expressions in Unix shell (yes, it’s not a script – you can just type commands one after another right in your shell prompt):

ubuntu$ START=1
ubuntu$ FINISH=5
ubuntu$ ELAPSED=`expr $FINISH - $START`
ubuntu$ echo $ELAPSED
4

But an even better way to evaluate arithmetic expressions is to use the built-in functionality of double brackets. Just continue typing in your shell:

ubuntu$ ELAPSED2=$(($FINISH - $START))
ubuntu$ echo $ELAPSED2
4

You see, everything that is inside double brackets gets evaluated and then this calculated value is assigned to the variable. I’m using a different variable – ELAPSED2 – to clearly show that this calculation has nothing to do with the original ELAPSED variable.

Assignment operator

If you wonder about the theory behind such a way of evaluating expressions, I should talk a bit about using variables in Unix shells.

As you probably remember, when you’re assigning your variable a new value, you’re simply specifying the name of the variable to the left of the expression, then use the = sign (it’s not just a symbol but an assignment operator by the way), and to the right of this operator goes the expression which represents the new value:

ubuntu$ MYVAR=123

Variable substitution operator

As you also remember, when you want to access the value of the variable, for example to print it out, you use the variable name with a leading $ sign. That’s because $, once again, isn’t just a fancy symbol, but in fact a special character in Bash – it’s a variable substitution operator, which means your Unix script substitutes the variable name you’re specifying in the script with the value of the variable:

ubuntu$ echo $MYVAR
3

Using the substitution operator ($) before the name of a variable is crucial. If you omit the $ sign, your shell will think you have simply passed it a text string, and will not recognize it to be a name of the variable:

ubuntu$ echo MYVAR
MYVAR

Expression evaluation explained

Coming back to arithmetic evaluations, you can probably see now that a line similar to this:

ubuntu$ ELAPSED=$(($FINISH - $START))

uses the variable substitution operator ($) and it’s therefore only logical that the further specified expression gets evaluated and the ELAPSED variable gets assigned with the resulting value.

Once again, using $ sign is crucial as without it your shell will not understand you:

ubuntu$ ELAPSED=(($FINISH - $START))
sh: syntax error near unexpected token `('

So, what do you think? I hope this makes sense to you, and will be sure to use this technique from now on – once you get used to it, it’s really easy to write and read shell scripts based on this functionality.

Let me know if there’s anything else you’d like to hear an explanation of! I’ll do my best! 🙂

See also:




Unix Scripting: Time and Date

If you have followed this blog for a while, you should remember how to use variables in Unix shell scripts.

Going further, I’d like to show you some basics of working with time and date in your scripts – generating all sorts of timestamps and timing some parts of your script for reporting purposes.

The history of (Unix) time

In case you didn’t know, time in Unix starts with a Unix epoch, sometimes also referred to as POSIX epoch. The Unix epoch is the time 00:00:00 UTC on January 1, 1970.

Most of Unix-like system today store and calculate this time according to Unix epoch, which means they all have an internal counter which counts the number of seconds elapsed since midnight of January 1, 1970.

As you can imagine, this is a pretty big number (it passed 1,000,000,000 seconds back in 2001, just so that you know), and reporting in directly would not be very useful to real humans. Because of this, most of Unix time and date reporting commands and functions do the conversion from Unix time into something more meaningful to the end user.

While for the vast majority of time and date related tasks this conversion is good, a task of timing certain events can sometimes benefit from using raw second counters and simply subtracting them when needed.

Getting Unix time and date

In Unix shells, the easiest way to get a current time and date is to use the date command:

ubuntu$ date
Tue Jun 10 10:46:07 IST 2008

date is a very smart command, and apart from this default behavior it supports template system for printing the current time and date – so you can use it to report only specific part of the time and date like the current hour or the day of a week or the year.

Read the man page for date (type man date in your shell prompt) to learn all the details, but this is how you use date output templates:

ubuntu$ date "+%b %d, %Y"
Jun 10, 2008

In this example, the %b parameter in this template represents the short name of the current month, %d is the day of the month, and %Y is the four-digit representation of the current year.

Timing parts of your Unix script

If you’re after timing some parts of your script, you’ll need to use two variables: one for saving the time before the start of an observed part of the script, and another one for saving the time after the same piece of code.

These two variables will be used to subtract the time and report the elapsed time in seconds, so in order to do this we’ll need the date command to report time in seconds. That’s why I’ve given you an introduction to Unix time earlier: date reports the number of seconds since the Unix epoch:

ubuntu$ date +%s
1213091896

If you run it just a few seconds later, you’ll see a different number:

ubuntu$ date +%s
1213091922

That’s why, if such values are saved and then subtracted, we’ll get the elapsed time in seconds.

Here’s a simple script showing how this is done:

#!/bin/bash
#
START=`date +%s`
echo "Script start time (Unix epoch): $START"
#
echo "- sleeping for 3 seconds..."
sleep 3
echo "- sleeping for 2 seconds more..."
sleep 2
#
FINISH=`date +%s`
echo "Script finish time (Unix epoch): $FINISH"
#
ELAPSED=`expr $FINISH - $START`
echo "Elapsed time: $ELAPSED"

And if you run it, you will see this output:

ubuntu$ /tmp/time-example.sh
Script start time (Unix epoch): 1213092131
- sleeping for 3 seconds...
- sleeping for 2 seconds more...
Script finish time (Unix epoch): 1213092131
Elapsed time: 5

That’s all I wanted to show you today. In future posts, I’ll show you a few more things you can do regarding timing and timestamps in Unix. Until then – good luck with your scripting, and feel free to ask if you need any more help!

See Also




How To Compare Directories in Unix

Certain situations require you to quickly confirm which files between two directories are different, and while your particular requirements may suggest writing a script for this task, I want to make sure you’re familiar with the basics first – majority of directory comparisons can be done using diff command (yes, that’s right – the same one used for comparing files).

Why compare directories?

First of all, let’s agree on why you may need to compare directories. There’s a few possible reasons:

  • comparing the amount of space consumed by two directories – this is the very first and the fasted way to compare directories because it gives you an idea how close in terms of space usage the directories are. For example, if you’re comparing two daily backups of the same piece of software, you normally don’t expect them to be vastly different.
  • identifying if some files are missing from one of the directories – can be useful when you want to make sure two directories with configuration files for a certain package are identical – files can be different, but the same files are present in the same locations for both directories
  • confirming if files in two directories are the same – a typical task when comparing your actual data against a backup copy. When something goes wrong, this is one of the first things you do to make sure all the important files are not only present, but are actually the same as they have been when you took the last backup copy
  • highlighting textual differences between files in directories – this is a useful exercise when you’re looking at two similar directories and expect only minor changes between the files – version numbers, different file or directory names hardcoded in various scripts, etc.

Comparing the size of two directories

I’m going to show you this trick before getting into details of using diff command. For size comparison, we should use the du command, it’s really easy.

The options used for the du command in the example below are: -s for summary (calculate the directory size based on the sizes of all the possible subdirectories it may have) and -k for kilobytes, so /usr/lib is roughly 400Mb in size as per the output below.

ubuntu$ du -sk /usr/lib /usr/lib64
404196  /usr/lib
0       /usr/lib64

This sample output will tell you that directories are vastly different, so that may save you time because you may choose not to compare anything file-by-file if one of the directories looks to be empty or really off space consumption wise.

Test setup for diff comparison exercises

For today’s post, I’ve created a set of directories and files to show how you can compare them. Here is the setup:

ubuntu$ find /tmp/dir1 /tmp/dir2
/tmp/dir1
/tmp/dir1/file1
/tmp/dir1/file2
/tmp/dir1/dir11
/tmp/dir1/dir11/file11
/tmp/dir1/dir11/file12
/tmp/dir2
/tmp/dir2/file1
/tmp/dir2/dir11
/tmp/dir2/dir11/file11
/tmp/dir2/dir11/file12
/tmp/dir2/file3

As you can see, I’ve got two directories: /tmp/dir1 and /tmp/dir2, with a dir11 subdirectory in each of them. There’s also a few files here and there, some of them missing from one of the directories specifically to be highlighted by our comparison exercises.

Basic diff usage for comparing directories

The easiest way to get started is to simply invoke diff command and specify two directories as command line parameters. Here’s what you will probably see:

ubuntu$ diff /tmp/dir1 /tmp/dir2
Common subdirectories: /tmp/dir1/dir11 and /tmp/dir2/dir11
diff /tmp/dir1/file1 /tmp/dir2/file11
Only in /tmp/dir1: file2
Only in /tmp/dir2: file3

This output confirms that /tmp/dir1 and /tmp/dir2 both contain a dir11 directory, and also shows that /tm/dir1/file1 and /tmp/dir2/file1 are actually different files even though they have the same name. By default, diff compares such files and you can see the result of each comparison in the output. Also included are pointers to the files which are present only in one of the compared directories: you can see that file2 can only be found in /tmp/dir1 and file3 was present only in /tmp/dir2.

Find which files are missing in one of the directories

From the example below, it is easy to deduct that the command line for identifying files missing in one of the directories will be this one:

ubuntu$ diff /tmp/dir1 /tmp/dir2 | grep Only
Only in /tmp/dir1: file2
Only in /tmp/dir2: file3

Highlight the different files, not the differences

If you’re only interested in files which exist in both directory structures, but are different – you can use a special command line option. It will simply point the files out, without getting into any further details. You’ll probably notice how this output is very similar to the default one:

ubuntu$ diff --brief /tmp/dir1 /tmp/dir2
Common subdirectories: /tmp/dir1/dir11 and /tmp/dir2/
Files /tmp/dir1/file1 and /tmp/dir2/file1 differ
Only in /tmp/dir1: file2
Only in /tmp/dir2: file3

Note how instead of showing the difference between file1 in /tmp/dir1 and /tmp/dir2, this time you only get told that these two files are different.

How to recursively compare directories

If you’re dealing with a complex directory structure, you’ll be glad to know that –recursive parameter for the diff command compares not only the immediate directories pointed to from the command line, but also walks through the full tree of subdirectories:

ubuntu$ diff --recursive --brief /tmp/dir1 /tmp/dir2
Files /tmp/dir1/dir11/file12 and /tmp/dir2/dir11/file12 differ
Files /tmp/dir1/file1 and /tmp/dir2/file1 differ
Only in /tmp/dir1: file2
Only in /tmp/dir2: file3

Feeling better now? Many directory comparison tasks can be accomplished using the diff command, but if you’re stuck with a particular problem which can’t be solved using my examples – please leave a commend and I’ll come up with a solution.

See also:




Get Username From UID in Unix

Finding out the username by user id (uid) in Unix is not as common a task as determining the uid by a username, but if you need to do it – I’ll show you how.

How to find the username using user id (uid)

Simply use the getent command. Most common use for it is to query the passwd source for a username, like this:

ubuntu$ getent passwd greys
greys:x:1000:113:Gleb Reys,,,:/home/greys:/bin/bash

however, if you query for a user id instead (1000 in this case), getent will work just as good:

ubuntu$ getent passwd 1000
greys:x:1000:113:Gleb Reys,,,:/home/greys:/bin/bash

That’s all there is to it! Enjoy!




Using variables in Unix shell scripts

Any Unix shell script longer than a line will most likely involve using variables. Variables are used to store temporary values to simply using them in various Unix commands of your script. The beauty of using variables is that they can be evaluated and set ones, but then reused as many times as you like without your shell interpreter having to re-evaluate them again.

Defining a variable in Unix shell

To specify a value for a variable, you need to decide on the variable name – can be any word or combination of English alphabet symbols and digits, and specify the value.

In Bourne shell (sh), Bourne Again Shell (bash) and Korn Shell (ksh), here’s how you would define a new variable and assign it a value:

CONFIG_FILE="/etc/myfile"

In C-Shell (csh), it’s done like this:

setenv CONFIG_FILE "/etc/myfile"

Basic variables usage

To access the value of a variable, you need to use the same variable name, but with a dollar sign in front of it, like this:

$CONFIG_FILE

Important: to set a new value to the variable, you use just the variable name like CONFIG_FILE, but to access this value later you need to use the $CONFIG_FILE form.

The most basic way to use variables is to assign them constant values at the beginning of your script. This is a good way to define locations of standard files or directories your script will work with, etc:

#!/bin/sh
#
CONFIG_FILE=/etc/myfile
MY_DIR=/etc
echo $CONFIG_FILE

Using output of Unix commands to set variables

One of the best things about shell scripting is that it’s very easy to use any Unix command to generate the output and use it to set the variable.

In this example, I’m running a date command and saving its output as values for my variables:

ubuntu$ cat /tmp/1.sh
#!/bin/sh
#
STARTED=`date`
sleep 5
FINISHED=`date`
#
echo "Script start time: $STARTED"
echo "Script finish time: $FINISHED"

If I run this simple script, I see the following:

ubuntu$ /tmp/1.sh
Script start time: Wed May 7 04:56:51 CDT 2008
Script finish time: Wed May 7 04:56:56 CDT 2008
The same approach can be used for practically any scenario.

Here’s an example of using uname command to extract some useful information about our system:

#!/bin/sh
#
STARTED=`date`
NODE=`uname -n`
OS=`uname -o`
CPU=`uname -p`
FINISHED=`date`
#
echo "Nodename: $NODE"
echo "OS type: $OS"
echo "Processor: $CPU"
echo "Script start time: $STARTED"
echo "Script finish time: $FINISHED"

And this is how it works:

ubuntu$ /tmp/1.sh
Nodename: ubuntu
OS type: GNU/Linux
Processor: unknown
Script start time: Wed May  7 05:05:31 CDT 2008
Script finish time: Wed May  7 05:05:31 CDT 2008

That’s it for today! Let me know what next topic about Unix shell scripting you’d like to see covered, and I’ll do my best to explain it in the coming posts.

Related books

If you want to learn more, here’s a great book:


linux-command-line-shell-scripting-bible
Linux Command Line Shell Scripting Bible




Unix filesystem basics: symlink example

I can see some of you have arrived to my Unix file types post looking for an example of using symlinks in Unix. Today I would like to give you a quick introduction into Unix symlinks.

What is symlink?

Symlink is a short name for symbolic link (sometimes also referred as soft link) is a special type of file in Unix, which references another file or directory. Symlink contains the name for another file and contains no actual data. To most commands, symlinks look like a regular file, but all the operations (like reading from a file) are referred to the file the symlink points to.

How to create a Unix symlink

Just to give you an example, here’s how a typical symlink can be created and verified.

First, we create a new text file called /tmp/file.1:

greys@ubuntu:~$ echo "Hello from file 1" > /tmp/file.1
greys@ubuntu:~$ cat /tmp/file.1
Hello from file 1

Now, we create a symlink called /tmp/file.2, which points to the original file of ours. We use the standard Unix ln command, first specifying the target file (the real file we want our symlink to point to), then specify the name of our symbolic link:

greys@ubuntu:~$ ln -s /tmp/file.1 /tmp/file.2

If we look at both files, here’s what we see:

greys@ubuntu:~$ ls -al /tmp/file*
-rw-r--r-- 1 greys greys 18 2008-02-07 22:22 /tmp/file.1
lrwxrwxrwx 1 greys greys 11 2008-02-07 22:23 /tmp/file.2 -> /tmp/file.1

If you notice, the /tmp/file.2 has an “l” in the long-format output of the ls command, which confirms it’s a symbolic link. You also can see right away where this symlink points to.

Just to confirm the typical behaviour of a symlink, here’s what happens when we try to show the contents of the /tmp/file.2: we see the contents of the file it points to, /tmp/file.1:

greys@ubuntu:~$ cat /tmp/file.2
Hello from file 1

How to remove a symlink

Guess what happens when you remove a symlink? Actually, not much. You only remove the symlink file itself, not the data file it refers to. Here’s what I mean:

greys@ubuntu:~$ rm /tmp/file.2
greys@ubuntu:~$ ls -al /tmp/file*
-rw-r--r-- 1 greys greys 18 2008-02-07 22:22 /tmp/file.1

While we’re at it, I would also like to explain what an orphan symlink is: it’s a symbolic link which points nowhere, because the original target file it used to point to doesn’t exist anymore.

Here is how an orphan symlink looks. First off, we recreate the symlink and verify it points to /tmp/file.1 once again:

greys@ubuntu:~$ ln -s /tmp/file.1 /tmp/file.2
greys@ubuntu:~$ ls -al /tmp/file*
-rw-r--r-- 1 greys greys 18 2008-02-07 22:22 /tmp/file.1
lrwxrwxrwx 1 greys greys 11 2008-02-07 22:38 /tmp/file.2 -> /tmp/file.1

Now, we simply rename the /tmp/file.1 file to /tmp/file.3:

greys@ubuntu:~$ mv /tmp/file.1 /tmp/file.3

This, naturally, makes /tmp/file.2 an orphan symlink which points to the old /tmp/file.1, but there isn’t a file like this anymore. Attempt to show the contents of /tmp/file.2 will thus fail:

greys@ubuntu:~$ ls -al /tmp/file*
lrwxrwxrwx 1 greys greys 11 2008-02-07 22:38 /tmp/file.2 -> /tmp/file.1
-rw-r--r-- 1 greys greys 18 2008-02-07 22:22 /tmp/file.3
greys@ubuntu:~$ cat /tmp/file.2
cat: /tmp/file.2: No such file or directory

See also:




How To Find Out a File Type and Permissions in Perl

A few months ago, I’ve given a really simple example of using Perl for parsing directory trees in Unix. If you looked closer at it, you would have noticed that the script was working fine, but showing file modes as strange large numbers which didn’t look like the usual file permissions you would expect. Today I’m going to explain why this happens, and show you how to find out a user type in Perl.

lstat and stat functions, return, among other things, the file mode value. While it looks confusing initially, it is in fact quite simply a combination field, which includes both the file type and all the permissions for it. If you print this field as a single decimal number, you will not recognize it, but if you simply convert it to octal, you will immediately start seeing the pattern:

Mysterious mode 33261 from the example below becomes 100755 when converted into octal, and you can easily see then the permission part of it: 0755.

In order to find out a file type in Perl, you need to be able to separate the type part from the permissions part of the file mode value. You can do this manually, but applying the bitmasks, but the easier way is to use the Fcntl ‘:mode’ Perl module.

Fcntl’:mode’ module contains all the constants and functions you need to extract the file type and permissions without complicating the task too much.

S_IMODE is the function which helps you extract the permissions:

        $permissions = S_IMODE($mode);

and S_IFMT is the function which extracts the file type:

        $filetype = S_IFMT($mode);

Now, obviously these extracted values are just the raw numbers, that’s why you’ll likely want to convert the permissions into octal, and to somehow connect the file type with the common symbolic notation, that’s exactly what I’m doing in today’s code example.

Here’s how it looks (I have the empty lines commented out just so that the code can be correctly shown in WordPress):

#!/usr/bin/perl
use Fcntl ':mode';
#
if ($ARGV[0] ne "") {
        $filename = $ARGV[0];
} else {
        print "Please specify a file!\n";
        exit;
}
#
if (($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat($filename)) {
        $user = getpwuid($uid);
        $group = getgrgid($gid);
#
        $ftypes[S_IFDIR] = "d";
        $ftypes[S_IFCHR] = "c";
        $ftypes[S_IFBLK] = "b";
        $ftypes[S_IFREG] = "-";
        $ftypes[S_IFIFO] = "p";
        $ftypes[S_IFLNK] = "l";
        $ftypes[S_IFSOCK] = "s";
#
        $permissions = sprintf "%04o", S_IMODE($mode);
        $filetype = S_IFMT($mode);
#
        $ftype = $ftypes[$filetype];
#
        print "File: $filename\n";
	printf "File mode: %o (%d)\n", $mode, $mode;
        printf "File type: %s (%o)\n", $ftype, $filetype;
        print "File permissions: $permissions\n";
        print "File size: $size\n";
        print "File owner user: $user\n";
        print "File group: $group\n";
} else {
        print "Please specify an EXISTING file!\n";
}

And this is how it works:

root@ubuntu:/usr/include# /tmp/stat.pl /tmp/stat.pl
File: /tmp/stat.pl
File mode: 100755 (33261)
File type: - (100000)
File permissions: 0755
File size: 918
File owner user: greys
File group: greys

See also: