Muffinresearch Labs by Stuart Colville

pwdn: show last n dirs of current directory | Comments (5)

Posted in Code, Linux/Unix on 2nd April 2008, 3:03 pm by Stuart

I’ve been playing around with some scripts of late to automate delivery of my various scripts and aliases to servers and in doing so I was looking back at the code I am calling to display the last two directories in my bash prompt. It made me think; why don’t I write a command in C to do that for me? Partly for the lulz and partly to get some experience playing with C; like learning any language there’s no substitute for diving right in by building something that no-one else will ever use!!

Here’s the code – please be gentle this is my first outing in C. If you can spot any room for improvements then be sure to let me know in the comments.

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    long size;
    char *buf;
    char *ptr;
    char delim[]="/";
    char *token;
    int numdir = argv[1] ? atoi(argv[1]) : 2;

    // get current working directory
    size = pathconf(".", _PC_PATH_MAX);
    if ((buf = (char *)malloc((size_t)size)) != NULL){
        ptr = getcwd(buf, (size_t)size);
        if (ptr==NULL) {
            return 1;
        }
    }else{
        return 1;
    }        

    // count instances of slash
    int count=0;
    char *slash = delim;
    while (*ptr != '\0'){
        if(*ptr == *slash){
            count++;
        }
        ptr++;
    }

    // Get tokens based on slash split
    int tokcount = 0;
    token = strtok(buf,delim);
    if (count > numdir){
       printf("...");
    }
    while (token != NULL)
    {
      tokcount++;
      if (tokcount > count - numdir) {
          printf ("/%s", token);
      }
      token = strtok (NULL,delim);
    }
    printf("\n");
    return 0;
}

To try this out grab pwdn.c and compile it for your system. (I’ve tested it out on linux and my mac and it works just fine)

To compile it:

gcc -o pwdn pwdn.c

If that worked a binary called pwdn will have been produced. Copy that somewhere in your path and then add the following to your .profile or .bashrc:

export PS1="\[\033[0;32m\]\u@\h \[\033[33m\]\$(pwdn 3)\[\033[0m\] \$ "

That’s an example prompt feel free to adapt as required. The program defaults to the last two dirs and shows “…” in front of the dirs when the display is truncated. If there’s only two dirs to show then you’ll see them all. If you want more than 2 dirs just pass in a different number as the first argument.

Remember to source your .bashrc or .profile so that you see the changes.

Here’s an example of the prompt in action:

muffin@shiva /Users/muffin $  cd ~/code/Python/Django
muffin@shiva .../Python/Django $ 

Just out of interest here’s the same thing in python:

#!/usr/bin/env python

from os import getcwd
from sys import argv
numdir = (len(argv) > 1) and int(argv[1]) or 2
cwd = getcwd()[1:].split("/")
print "%s%s" % (len(cwd) > numdir and ".../" or "/", '/'.join(cwd[-numdir:]))

Post Tools

Comments: Add yours

1. On April 6th, 2008 at 10:44 pm James Aylett said:

Cool :-) — welcome to C.

Some stylistic things: you should use strtol() in preference to atoi(); also ++n is generally better practice than n++ (it makes no difference in a lot of cases, and certainly shouldn’t in this, but it’s good practice unless you actually need post-increment). You have char* slash, delim; when you only need delim (I think). Finally, you have one if(…) without using braces, which I’d advise against because of the potential for a dangling else in future.

One buggette in cut and paste: printf(“n”); probably wants to be printf(“\n”); two lines from the end of main().

You aren’t handling errors from things like malloc, pathconf (unlikely), getcwd (change permissions underneath the calling shell) — this will result in abends (I was expecting a segfault from getcwd failure, but I actually got a bus error; whichever, it isn’t good).

Your python script has a bug in it as well: invoke without parameters and you get an IndexError. This isn’t the case in the C version, because argv will always be terminated by a NULL pointer at the end of the list (at argv[argc] — at least in C99, see 5.1.2.2.1 2 of WG14/N1124 for instance). In theory argc can be 0, in which case argv[1] isn’t valid; however that’s not going to happen on any Unix I’m aware of. (While talking about this, main should really have a return type of int.)

All minor things, though. I love the fact that the C code will be more readable to people with no knowledge of the language than the PS1 value that uses it :-)

2. On April 7th, 2008 at 8:28 am Stuart Colville said:

@James: Thanks for the pointers!! (No pun intended) I’ve added some basic error handling, fixed the missing brackets, return type of main. When you say strtol don’t you mean strtoi I don’t want to return a long?

The printf was PHP eating slashes – fixes that now so it displays correctly.

Re: pre-increment isn’t the small performance increase removed by the compiler?

Fixed the bug in the python version – that’ll teach me for throwing the comparison together :-)

3. On April 7th, 2008 at 11:06 am Caius Durling said:

A ruby version.

#!/usr/bin/env ruby

# Prints out the last n dirs of current directory

# The number of dirs to show in path
dirnum = (ARGV[0] || 2).to_i
# Grab current directory path and convert to array
pwd = Dir.pwd.split("/")
# Chop off the first element if its an empty string
pwd = pwd.slice( 1, pwd.length ).compact if pwd.length > 1 && pwd[0].empty?

if pwd.length > dirnum
 # The path is truncated
 puts ".../" + pwd.slice( -dirnum, dirnum).join("/")
else
 # The path is absolute
 puts "/" + pwd.join("/")
end 
4. On April 7th, 2008 at 3:15 pm James Aylett said:

1. strtoi vs strtol. strtoi isn’t part of C99, and doesn’t exist on my linux box, Mac OS 10.4, or Solaris 10. (I haven’t seen strtoi anywhere, come to think of it.)

You probably actually want strtoul. There’s nothing you’re doing that will be problematic with an unsigned long, after all.

2. free(NULL) isn’t a great idea ;-) (you do it if malloc fails)

3. pre- versus post- increment. Umm. In most cases, the compiler will happily optimise the difference away. In some cases it might not be able to, although with out of order compilation (ie: independent actions happen out of sequence) it can still get it right in a lot of cases. You can probably trip it up with something horrible, and you can always force the issue using volatile, providing you don’t mind people looking at you strangely.

In C++ it makes more of a difference, because your type might be a class not a primitive, and most people implement pre-increment in terms of post-increment, so you’re paying an extra method call.

It’s a stylistic thing that under certain circumstances will make things faster, and shouldn’t ever make them slower. I’ll freely admit that I don’t always bother ;-)

4. that ruby doesn’t work for me – you want ARGV[0].to_i on line 3.

5. On April 8th, 2008 at 1:56 am Stuart Colville said:

@James: Thanks for the corrections, and I’ve updated Caius’ code accordingly.







XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



GNU screen: open tab in current working directory|(1)

A nice trick for having screen open a new tab in the same directory as the one you’re currently in. To use it add it to your .screenrc

# Open new window in current dir.
bind c stuff "screen -X chdir \$PWD;screen^M"
bind ^c stuff "screen -X chdir \$PWD;screen^M"

Hat tip: mteckert on SuperUser.com

Ubuntu: add-apt-repository: command not found|(2)

When you’re using a minimal Ubuntu install if you find the ‘add-apt-repository’ command is missing (it’s useful for adding PPAs and other repositories), then simply run:

sudo apt-get install python-software-properties

Photos on Flickr

© Copyright 2004-12 Stuart Colville, all rights reserved. May contain traces of Muffin. Powered by WordPress. Hosting by Slicehost.com This page was baked in 0.538s.