LinuxQuestions.org
Register a domain and help support LQ
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

Simple C: Beyond Bash Part 1 - String lists and pipes

Posted 01-26-2012 at 08:03 AM by rainbowsally
Updated 04-05-2012 at 07:54 PM by rainbowsally

[Note, this is marked for POSSIBLE deletion in the future by the author. This version is not exactly the same as the one in libLQ. In fact it may be removed a bit later, though it has some advantages over the the libLQ version in that it uses REAL char** types which behave exactly like any other char** type. The libLQ version monkeys with the base address in order to tuck an 'argc' below the first arg and thus avoid repeatedly literally 'count'-ing the string size. It is is a subclass of an object list and therefore requires a special slist_delete() to free all the memory. Probably shouldn't even be a char** type anymore but since we started out down the wrong path... we'd better try to just live with it rather than break too much older code.]

Simple C: Beyond Bash Part 1 - String lists and pipes

CHANGELOG:
* Jan 26, 2012 - Added "free(buf)" in list_readPipe()

SEE NOTE ABOVE.

Features:
  • Simple sting list functions
  • Including a way to read and write lists to and from files
  • Using the increased type safety of C++ to avoid subtle errors, at least for the first cut.
  • Linking object files.
  • An application that can look up all dependencies in all files in a directory, for use with our make-based system, etc. (in part 2)

One of the main coolnesses of that crazy 'bash' interpreter is the way it works with "pipes".

You thought I was going to say "args lists" or "lists of strings"? Heck NO! Bash is horrible for working with strings. I don't care what they say about [R]TFM, making sense of bash is at times like making sense of nonsense, and so it's no wonder there are so many other programs like perl, tcl, python, etc., getting into the picture.

But C. That's where WE have the control, and almost as much freedom as assembler (and in fact even that if we need it). And if you think you need a simplified syntax, or relaxed type safety, "Go macros, young man, go macros." There are some examples in the recent "asm" blog entry and more in the first "simple c" entry to show the kind of things that are possible.

With a good debugger (kdbg is dynamite) and an [R]TFM that makes sense (again I say, bookmark the alphabetical list in "Functions and Macros" section of the libc docs), and once you get past the seemingly wild spew of error messages you will encounter as a newbie (they are cumulative, the first will trigger others so they do clear up as quickly as they appear), you may just find that the best thing about bash is that it can run C apps. :-)

Don't get me wrong. Bash is good for what it's good for, BUT. That's where it ends.

So...

There two main things we need in order to get some real power with lists (such as args lists and piped strings). And that's what we will throw together today. This will be a two part post. The first part is the lists, a bit of compiling and linking, the second part is an application that may be useful for other things, but certainly useful for make-based install/uninstall functions, which can indeed work with other packages (rpm and deb) to do a much safer first run -- because it can roll back in case of an error or a dangerous overwrite. (see previous "make-base" posts).

A couple of notes here. Skip this if you just want to get to the goodies.

And while you're contemplating the warnings about the make-based setup, consider the fact that rpm and deb do not even warn you.

Ever got your entire kde and x11 wiped out by installing an file using apt-get or muon? Well... I have.

Ever wondered how libffi45 got to be 60 megs instead of 280 K?

Who's checking up on those folks (openSUSE)?

How about "checkinstall". What happens if it crashes you. How are you supposed to use the alleged "backup file"? (openSUSE and others.)

So be forewarned. It's nice to at least know what you're getting into.
:-)

ALSO

I'm using kdevelop3 (retro) and it just freakin' smokes.

kdevelop4 on the other hand is a shockingly unrestrained resource/disk hog, and apparently an amalgam of development philosophy paradoxes. Got rid of regex and added VI mode? OUCH! Lot's of sense that makes.

I really like kdevelop3 though. It will open source files to the exact lines where I make mistakes so I can correct them and when parameters are out of order or something, it will even open the source header in the system files to show what I should have done. And the grepper can't be beat.

But the choice is yours. Retro can be risky. But personally I think that it's well worth the risk, and I have never had a problem with kdevelop3.

Do check out the 'retro' blog entry about kdevelop if you need a leg up, trying it out tho.

Or if anyone has a better way get kdevelop3 into their allegedly new and improved KDE system, please let us know!
:-)

Back on the ranch...

Here's the header file, and I've included the pipe functions along with the string list functions.

[There's no plan (as of the time of this writing) to add plain text file read/write functionality but that might be a great project for newbies. :-) ]

file: slist.h
purpose: describes syntax and functionality of various string list calls.
Code:
// slist.h

#ifndef SLIST_H
#define SLIST_H

// TODO:
// slist_insert(char*** plist, s, idx)   -- insert new item at idx
// slist_moveList(plist1, idx, plist2, delete_flg)  -- overwrite and append,
//                                                 -- [empty or delete list2]
// slist_trim(plist, idx)                -- remove items from idx to end
// slist_shift(plist, n)                 -- remove first n items from list

// returns an initialized pointer to a list base
char** slist_new();

// removes item and deletes the base
void slist_delete(char** list);

// returns number of items in the list
int slist_count(char** list);

// removes items, does not resize or delete the base
void slist_clear(char** list);

// adds a new items at the end of the list
void slist_append(char*** plist, const char* s);

// removes (and frees) item at idx. Does not resize the list
void slist_remove(char*** plist, int idx);

// adds list list2 to list1, does not delete list that's added
void slist_addList(char*** plist1, char** list2);

// remove one trailing newline char from a string
// static void trim_newline(char* buf);

// Loads list with strings from a piped command,
// returns 0 on success
int slist_readPipe(char*** plist, const char* pipecmd);

// writes list through pipe, returns 0 on success.
int slist_writePipe(char*** list, const char* syscmd);

#endif // SLIST_H
And here's the code. It's in C++ but could probably be turned to a C file if compiled with the -std=c99 switch or the equivalent.
file: slist.cpp
purpose: functions to create and work with lists of string objects
Code:
// slist.cpp -- lists of strings in the format of an args list, 0 terminated.

#include <malloc.h> // malloc and free
#include <string.h> // str*() funcs

#include "slist.h"

// returns an initialized pointer to a list base
char** slist_new()
{
  char** p = (char**)malloc(sizeof(char**));
  *p = 0;
  return p;
}


// removes item and deletes the base
void slist_delete(char** list)
{
  int i = 0;
  while(list[i])
    free(list[i++]);
  free(list);
}

// returns number of items in the list
int slist_count(char** list)
{
  int i = 0;
  while(list[i])
    i++;
  return i;
}

// removes items, does not resize or delete the base
void slist_clear(char** list)
{
  int i = 0;
  for(int i =  0; list[i] != 0; i++)
  {
    free(list[i]);
    list[i] = 0;
  }
}

// adds a new items at the end of the list
void slist_append(char*** plist, const char* s)
{
  int count;
  char** list = *plist;
  for(count = 0; list[count]; count++)
    ; // noop
  list = (char**)realloc(list, sizeof(char**) * (count + 2));
  list[count] = strdup(s);
  list[count + 1] = 0;
  *plist = list;
}

// removes (and frees) item at idx. Does not resize the list
void slist_remove(char*** plist, int idx)
{
  char** list = *plist;
  int len = slist_count(list);
  if((idx < 0) || (idx > len))
    return;
  free(list[idx]);
  for(int i = idx; i < len; i++)
    list[i] = list[i+1];
  // *plist = list; -- already set since no resizing is done
}


// adds list list2 to list1, does not delete list that's added
void slist_addList(char*** plist1, char** list2)
{
  int len = slist_count(list2);
  for(int i = 0; i < len; i++)
    slist_append(plist1, list2[i]);
}


// remove one trailing newline char from a string
static void trim_newline(char* buf)
{
  char* p = buf + strlen(buf) -1;
  if(*p == '\n')
    *p = 0;
}

// Loads list with strings from a piped command,
// returns 0 on success
int slist_readPipe(char*** plist, const char* pipecmd)
{
  size_t bufsize = 4096;
  char* buf = (char*) malloc(bufsize);
  char** list = *plist;
  FILE* fp = popen(pipecmd, "r");
  if(! fp) return 1;
  while(! feof(fp))
  {
    *buf = 0;
    getline(&buf, &bufsize, fp);
    
    // done if nothing is read
    if(! *buf)
      break;
    
    trim_newline(buf);
    slist_append(&list, buf);
  }
  fclose(fp);
  
  // save the result and return ok
  *plist = list;
  free(buf); // ADDED -rs
  return 0;
}

// writes list through pipe, returns 0 on success.
int slist_writePipe(char*** plist, const char* pipecmd)
{
  char** list = *plist;
  FILE* fp = popen(pipecmd, "w");
  if(! fp) return 1;
  for(int i = 0; i < slist_count(list); i++)
  {
    char* line = list[i]; // to see in debugger
    fprintf(fp, "%s\n", line);    
  }
  fclose(fp);
  return 0;
}
To test and start messing around with this, create a basic main.cpp template like so:
Code:
// some commonly used c headers, no harm in overkill
#include <stdio.h>    // printf, sprintf, etc.
#include <stdlib.h>   // exit()
#include <unistd.h>   // tons of stuff
#include <malloc.h>   // memory allocation
#include <string.h>   // strings and block comparisons and moves

#include "slist.h"    // all we need is the header

void dbg(){} // place a breakpoint here that won't move even if main() does.

int main(int argc, char** argv)
{
  dbg(); // best if viewed with kdbg.
  
  // add test code here
  
  return 0; // no errors
}
While it's quite possible to also "include" the cpp file, let's get some experience linking object files. Here's how to compile this with debug info so you can run the app in a debugger.

Code:
COMPILE() # basename
{
  g++ -c $CFLAGS $1.cpp -o $1.o
}

LINK()
{
  g++ $LDFLAGS $OBJ -o $1
}
Those two functions are now defined in the environment. This is similar to what makefiles do.

In the COMPILE funcion the -c switch tells gcc/g++ to only compile. Do not link. The parameter "basename' is the name of the file minus the "cpp" extension in order to simplify the definition.

Now we have two simple commands to compile and link the program.

Type
Code:
COMPILE slist
and make sure 'slist.o' is created.

Type
Code:
COMPILE main
check for main.o too

Now we have the object files and we can create the OJB definition like this.
Code:
OBJ=`ls *.o` # Think about this.  :-)  Automatic object listing.
echo $OBJ # and verify that main.o and slist.o are both compiled.
Ready to link?
Code:
LINK output
Notice that the output file name will be "output". Change that to anything you like.

And we notice that CFLAGS haven't been defined?

If you want to compile with debug info in the files,
Code:
CFLAGS="-g3"
And repeat both compiles and link.

Check the size of the output. ['du -cb <filename>' works from the commandline if you prefer not to have to switch windows (and press F5 to update the browser).]

If you want an optimized output file, use '-O2' instead.

Check the size.

There are other g<N> levels for debug output and there are other O<N> levels for optimization but these two are the most useful.

I can see that it's time to fire up Ye Olde 'hlp' app. :-)

See early blog entries on "KDE Utils" for a GREAT help doc viewer.

Type
Code:
hlp gcc
to get more ideas about what you can do.

And again, even if you don't want kdevelop3, kdbg (version 5 or greater) will knock your socks off. Check it out with the -g3 version of your executable.

Note: You might be thinking, well, that's so easy, why not create a permanent subroutine file for COMPILE and LINK? No, don't do that. I mean, you could but, make and makefiles have some very interesting qualities that will quickly obsolete this simple tool. Similar, but different. :-) Maybe we should peek into that subject soon too, even if it does look like this is becoming a C/C++ programming blog. ;-)

Coming Up: Part 2.

Let's make a utility to scan a directory branch and find ALL dependencies in ALL the binary files.

You can forget about bash here.

Warp 9... Engage.

:-)
Posted in Uncategorized
Views 6354 Comments 2
« Prev     Main     Next »
Total Comments 2

Comments

  1. Old Comment
    Code:
    OBJ=`ls *.o` # Think about this.  :-)  Automatic object listing.
    echo $OBJ # and verify that main.o and slist.o are both compiled.
    Don't parse ls:

    Code:
    OBJ=*.o
    echo $OBJ
    Posted 02-04-2012 at 09:34 PM by ntubski ntubski is offline
  2. Old Comment
    For a straight shell script you're right.

    Thanks for the comment. :-)
    Posted 02-11-2012 at 11:11 AM by rainbowsally rainbowsally is offline
 

  



All times are GMT -5. The time now is 01:00 PM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration