Translate

Archives

Korn Shell 93 Stat Builtin

One of the builtin commands that is missing in ksh93, in my humble opinion, is a builtin similar to stat(1) which would return information about a file. Here is my initial implementation of a stat builtin. The output is a compound variable whose subvariables contain the contents of the various fields of the stat(2) structure. If you are unfamiliar with compound variables, see my previous post for an eluridation.

/*
**  FPMurphy  2009-01-03
**
**  License: Common Public License Version 1.0
**
*/

#pragma prototyped

#include "defs.h"
#include "builtins.h"
#include "path.h"
#include <tm.h>

/* macro to create subvariables */
#define CREATE_CVE(X,Y,Z) \
   strcpy(b,(X)); \
   np = nv_open(buf, shp->var_tree, NV_NOASSIGN|NV_VARNAME); \
   nv_putval(np, (char*)(Y), (Z) ); \
   nv_close(np)

static char strperms_buf[30];

static const
char sh_optstat[] =
   "[-?\n@(#)$Id: stat 2009-01-03 $\n]"
   "[-author?Finnbarr P. Murphy fpm at hotmail.com ]"
   "[-license?http://www.opensource.org/licenses/cpl1.0.txt]"
   "[+NAME? stat - get file status]"
   "[+DESCRIPTION?\bstat\b creates the compound variable \avar\a corresponding"
       " to the file given by the pathname \afile\a.  The elements of \avar\a"
       " are the names of fields in the \astat\a structure with the \bst_\b"
       " prefix removed, together with the basename of \afile\a.]"
   "\n"
   "\nvar file\n"
   "\n"
   "[+EXIT STATUS?]{"
       "[+0?Success.]"
       "[+>0?An error occurred.]"
   "}"
   "[+SEE ALSO?\bstat\b(1),\bstat\b(2)]"
;

/* stringify the permission bits */
static char *
strperms(char * p, mode_t mode)
{
   char ftype = '?';

   if (S_ISBLK(mode))  ftype = 'b';
   if (S_ISCHR(mode))  ftype = 'c';
   if (S_ISDIR(mode))  ftype = 'd';
   if (S_ISFIFO(mode)) ftype = '|';
   if (S_ISLNK(mode))  ftype = 'l';
   if (S_ISREG(mode))  ftype = '-';

   sfsprintf(p, 30, "\\0%010lo %c%c%c%c%c%c%c%c%c%c %c%c%c\0",
   mode, ftype,
   mode & S_IRUSR ? 'r' : '-',
   mode & S_IWUSR ? 'w' : '-',
   mode & S_IXUSR ? 'x' : '-',
   mode & S_IRGRP ? 'r' : '-',
   mode & S_IWGRP ? 'w' : '-',
   mode & S_IXGRP ? 'x' : '-',
   mode & S_IROTH ? 'r' : '-',
   mode & S_IWOTH ? 'w' : '-',
   mode & S_IXOTH ? 'x' : '-',
   mode & S_ISUID ? 'U' : '-',
   mode & S_ISGID ? 'G' : '-',
   mode & S_ISVTX ? 'S' : '-');

   return(p);
}


int
b_stat(int argc, char *argv[], void *extra)
{
   register Shell_t *shp = ((Shbltin_t*)extra)->shp;
   register Namval_t *np;
   register int n;
   struct stat statb;
   char buf[100];
   char *b;

   while (n = optget(argv, sh_optstat)) switch (n) {
      case ':':
         errormsg(SH_DICT, 2, "%s", opt_info.arg);
         break;
      case '?':
         errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg);
         break;
   }

   argc -= opt_info.index;
   argv += opt_info.index;
   if (argc!=2)
       errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0));

   /* stat the file */
   if (stat(argv[1], &statb) < 0)
       errormsg(SH_DICT, ERROR_system(1), "%s: stat failed", argv[1]);

   strcpy(buf, argv[0]);
   b = buf;
   while (*b) b++;

   /* create compound variable */
   np = nv_open(buf, shp->var_tree, NV_NOASSIGN|NV_VARNAME|NV_ARRAY );
   if (!nv_isnull(np))
      nv_unset(np);
   nv_setvtree(np);
   nv_close(np);

   /* create compound variable elements */
   CREATE_CVE(".name", path_basename(argv[1]) , NV_RDONLY);
   CREATE_CVE(".atime", &statb.st_atime, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".ctime", &statb.st_ctime, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".mtime", &statb.st_mtime, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".uid", &statb.st_uid, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".gid", &statb.st_gid, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".size", &statb.st_size, NV_RDONLY|NV_INTEGER|NV_LONG);
   CREATE_CVE(".dev", &statb.st_dev, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".ino", &statb.st_ino, NV_RDONLY|NV_INTEGER|NV_LONG);
   CREATE_CVE(".nlink", &statb.st_nlink, NV_RDONLY|NV_INTEGER);
   CREATE_CVE(".mode", strperms(strperms_buf, statb.st_mode), NV_RDONLY);

   return(0);
}

This code was tested using ksh93t+.

You can embed a man page into the builtin as done in the above source code.  Most, if not all, of the ksh93 commands have such embedded man pages.

Here is the output from stat –help.

$ stat --help
Usage: stat [ options ] var file
$

and here is the output from stat –man.

$ stat --man
NAME
   stat - get file status

SYNOPSIS
  stat [ options ] var file

DESCRIPTION
  stat creates the compound variable var corresponding to the file given 
  by the pathname file. The elements of var are the names of fields in the 
  stat structure with the st_ prefix removed, together with the basename 
  of file.

EXIT STATUS
    0     Success.
    >0    An error occurred.

SEE ALSO
  stat(1),stat(2)

IMPLEMENTATION
  version         stat 2009-01-03
  author          Finnbarr P. Murphy fpm at hotmail.com
  license         http://www.opensource.org/licenses/cpl1.0.txt
$

Here is an example of using the stat builtin to get information about a file called tksh.

$ stat fileinfo ./tksh
$ print $fileinfo
( atime=1231192796 ctime=1231192291 dev=2065 gid=500 ino=50897 
mode='0100755 -rwxr-xr-x ---' mtime=1231192291 name=tksh nlink=1 size=2171604 uid=500 )
$ print ${fileinfo.atime}
1231192796
$ printf "%(%Y-%m-%d %H:%M:%S)T\n" "#${fileinfo.atime}"
2009-01-05 16:59:56
$

As you can see the stat builtin gets the file statistics using stat(2), creates a compound variable with the specified name, i.e. fileinfo, and then creates a series of subvariables (atime, ctime, mtime, etc. ) whose names correspond to the fields of the stat(2) structure.  This compound variable is then available to you to use as necessary within your shell script.  Rather than having to handle a series of variables, one per stat(2) structure field, you simply have to deal with single compound variable, i.e. fileinfo.

As always, email me if you have any questions.
 

Comments are closed.