tag:blogger.com,1999:blog-67506819001669266422009-02-21T03:55:32.192-05:00ljstechTech notesLouhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.comBlogger11125tag:blogger.com,1999:blog-6750681900166926642.post-77617614645594017462008-08-28T15:01:00.004-05:002008-08-28T15:34:54.529-05:00Meta Can Be Readable<p>Stumbled upon this post today on why you should <a href="http://railsontherun.com/2008/5/4/avoid-using-metaprogramming">avoid meta programming</a>. The author contends that meta programming in ruby can, one, slow down your code, and two, it can <em>make you code too hard to follow</em>.</p> <p>If you read the article comments, you'll see that some responders have shown various ways of speeding up the code example he gave, so I'll leave that part alone. Instead let's focus on the readability issue. While you can definitely take things too far and over generalize a solution, there are some steps you can take to make the code easier to follow. Consider this refactoring:</p> <pre class="twilight"><span class="Keyword">module</span> <span class="Entity">TimeMethods</span> <span class="Keyword">def</span> <span class="Entity">self.define_time</span>(<span class="Variable">sym<span class="Variable">,</span> secs</span>) module_eval <span class="String"><span class="String">&lt;&lt;-EOC</span>, __FILE__, __LINE__</span> <span class="String"> def <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>sym<span class="StringEmbeddedSource">}</span></span>; self * <span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>secs<span class="StringEmbeddedSource">}</span></span> end</span> <span class="String"> alias_method :<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>sym<span class="StringEmbeddedSource">}</span></span>s, :<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>sym<span class="StringEmbeddedSource">}</span></span></span> <span class="String"><span class="String"> EOC</span></span> <span class="Keyword">end</span> <span class="Support">Numeric</span>.<span class="Entity">send</span>(<span class="Constant"><span class="Constant">:</span>include</span>, <span class="Variable">self</span>) define_time <span class="Constant"><span class="Constant">:</span>second</span>, <span class="Constant">1</span> define_time <span class="Constant"><span class="Constant">:</span>minute</span>, <span class="Constant">60</span>.<span class="Entity">seconds</span> define_time <span class="Constant"><span class="Constant">:</span>hour</span>, <span class="Constant">60</span>.<span class="Entity">minutes</span> define_time <span class="Constant"><span class="Constant">:</span>day</span>, <span class="Constant">24</span>.<span class="Entity">hours</span> define_time <span class="Constant"><span class="Constant">:</span>week</span>, <span class="Constant">7</span>.<span class="Entity">days</span> define_time <span class="Constant"><span class="Constant">:</span>year</span>, <span class="Constant">364.25</span>.<span class="Entity">days</span> <span class="Keyword">end</span> puts <span class="Constant">2</span>.years </pre> <p>All I did was move the tricky "magic" part into it's own method. Any class or module methods that we create will be available to us inside of that Class or Module definition. That makes the final part very where we use <tt>define_time</tt> very readable because it's declarative and intention revealing.</p> <p>Don't be afraid of ruby meta programming. I think Giles Bowkett puts it perfectly when he says that <a href="http://mwrc2008.confreaks.com/03bowkett.html">it's not meta programming, it's just programming</a>. We should use the same tools to refactor it as any other code.</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-7761761464559401746?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-48892556450602186342008-02-01T13:38:00.001-05:002008-02-01T13:44:56.227-05:00Ruby: Trivial Memoisation with Hashes<p>Using the block form of ruby's Hash constructor makes memoisation really easy. Here's some code that does has a hash do recursive lookups upon itself. Pretty nifty, eh?</p> <pre class="twilight"><span class="Keyword">require</span> <span class="String"><span class="String">'</span>logger<span class="String">'</span></span> logger <span class="Keyword">=</span> <span class="Support">Logger</span>.<span class="Entity">new</span>(<span class="Variable"><span class="Variable">$</span>stdout</span>) fib <span class="Keyword">=</span> <span class="Support">Hash</span>.<span class="Entity">new</span> {|<span class="Variable">fib</span>, <span class="Variable">x</span>| logger.<span class="Entity">debug</span> <span class="String"><span class="String">&quot;</span>Calculating fib(<span class="StringEmbeddedSource"><span class="StringEmbeddedSource">#{</span>x<span class="StringEmbeddedSource">}</span></span>)...<span class="String">&quot;</span></span> fib[x] <span class="Keyword">=</span> <span class="Keyword">if</span> [<span class="Constant">1</span>,<span class="Constant">2</span>].<span class="Entity">include?</span> x <span class="Constant">1</span> <span class="Keyword">else</span> fib[x<span class="Keyword">-</span><span class="Constant">1</span>] <span class="Keyword">+</span> fib [x<span class="Keyword">-</span><span class="Constant">2</span>] <span class="Keyword">end</span> } puts fib[<span class="Constant">5</span>] puts fib[<span class="Constant">7</span>] </pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-4889255645060218634?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-88797195596812789582007-03-03T21:04:00.000-05:002007-03-03T22:09:06.640-05:00Splat!!<p>Oh the things you can learn by reading <a href="http://code.whytheluckystiff.net/">_why's code</a>. It really is like being in the mind of a genius.</p> <p>Over the past few days I've been playing around with the Camping &ldquo;micro-framework.&rdquo; Like <a href="http://www.rubyonrails.com/">Rails</a>, Camping a web framework built on the <acronym title="Model/View/Controller">MVC</acronym> concept. While Rails aims to be feature rich, however, camping blazes off in the direction of keeping small and light weight. Oh, and did I mention exceedingly clever? Take the time to read through the source&mdash;the <a href="http://code.whytheluckystiff.net/camping/browser/tags/1.5/lib/camping-unabridged.rb">unabbridged version</a> is better for this&mdash;there's lots of goodies in there. <p>Anyway. One of the cool tidbits in there is defining a <code>to_a</code> method for using with the splat operator. The splat "<code>*</code>" is used to explode an array into the parameter list of a method. For example, assume the following function:</p> <pre><code>def foo a,b puts "You gave me a(n) #{a}, and also a(n) #{b}. How very generous of you!" end</code></pre> <p>Normally the function would be called passing it two parameters:</p> <pre><code>foo "Apple", "Banana"</code></pre> <p>If you use the splat you can break up an array into the method:</p> <pre><code>fruit = %w|Kiwi Grape| foo *fruit</code></pre> <p>Now, none of this is all too earth shattering; however, it does lead to some clever uses when paired with a suitable <code>to_a</code> class method.</p> <pre><code>class ContinentalBreakfast def initialize(*offerings) @offerings = offerings end def to_a # Pick some random fruit... plate = [] table = @offerings.dup 2.times do f = table[rand(table.length)] table.delete(f) plate &lt;&lt; f end plate end end</code></pre> <p>Cool. Now we can ask somebody to bring us back a bit of a snack:</p> <pre><code>foo *ContinentalBreakfast.new('grape','pear','melon','strawberry')</code></pre> <p>Pretty neat, eh?</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-8879719559681278958?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-30441296426616237182007-02-22T19:05:00.000-05:002007-02-22T20:04:41.844-05:00Ruby Lib Path Manipulations<p>Mucking around with the library search path in ruby isn't exactly the hardest thing in the world. It's just a matter of fiddling around with a horrible looking perlish global variable and using __FILE__ to munge the right path together. Again, nothing too difficult but it's annoying and ugly&mdash;totally out of place next to all that beautiful ruby code.</p> <p>For the most part, this isn't much of a problem for libraries. In fact, you don't normally want to hard code paths anyway. Typically a flag is passed to the ruby interpreter:</p> <pre><code>$ ruby -Isome/path foo.rb</code></pre> <p>However, in some cases you still need to change the path in the code. Likely the most common place this shows up is within test/spec files. Does this look familiar?</p> <pre><code># some_cool_spec.rb $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'lib_under_test' context '...' do # snip end</code></pre> <p>It’s so ugly, right? To me it's quite the eyesore so I wrote the following really simple library (<code>rubylib.rb</code>) to fix it up a bit:</p> <pre><code>module LibCommands def lib(*path_segs) $:.unshift File.join(*path_segs) end def lib_rel(*path_segs) file = caller.first.split(':').first lib File.dirname(file), *path_segs end end Object.send :include, LibCommands</code></pre> <p>Now when you want to add something to the load path, you can just use the lib method:</p> <pre><code>lib 'path', 'to', 'library'</code></pre> <p>It's even better when dealing with relative paths. <code>lib_rel</code> specifies the directory to add relative to the file it's called from&mdash;rather than the current working directory, which is probably different. That makes the above rspec:</p> <pre><code># some_cool_spec.rb lib_rel '..', 'lib' require ‘lib_under_test’ context '...' do # snip end</code></pre> <p>Nice, eh? Sure, but you still need to require rubylib.rb first. You can fix that up with a few little tricks used by <a href="http://www.rubygems.org/">rubygems</a>. First, create a<code>ubylib.rb</code> that just requires <code>rubylib.rb</code>. That way it looks nicer when you add it in as a flag to ruby.</p> <pre><code>$ ruby -rubylib foo.rb</code></pre> <p>The next level of laziness is to add it you your <span class="caps">RUBYOPT</span>. With the environment variable set the two commands are available without any explicit requires:</p> <pre><code>$ export RUBYOPT="${RUBYOPT} -rubylib" $ ruby foo.rb</code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-3044129642661623718?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com1tag:blogger.com,1999:blog-6750681900166926642.post-40369231891010315132007-02-01T21:53:00.000-05:002007-02-02T17:11:14.662-05:00Blogger: the Switch<p>As you may have noticed, the blog is looking a little different today. Previously, I was hosting a <a href="http://trac.typosphere.org/">typo</a> blog on my VPS account, which was working out perfectly well enough, but it brought up a few issues: every time I went to go publish an article I would think about how typo didn't really work the same way I did.</p> <p>This problem is pervasive with me. There's likely always something I should be doing at the moment, but I get it in my head that it's not exactly the way I'd like it so I go off to write my own program to do X—I really need to cut that out.</p> <p>So I decided rather than try to rewrite my own blog engine, or hack on something to get it into shape, I could better spend my time actually writing articles. We'll see how this works out. It's rather nice that blogspot allows you to use your own domain name as well, which was one of the last sticking points.</p> <p>We'll see how the little experiment goes. Sure it's going to kill me to write out these posts in the web form for a bit, but I'm sure I'll figure out the api quickly. I've really crippled myself with vim. I can't even type anymore without it.</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-4036923189101031513?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-5139075705071340222006-12-12T20:04:00.000-05:002007-02-09T08:03:37.190-05:00Shell Tasks? In Haskell?<p>No. Your eyes are not deceiving you: I am in fact suggesting that Haskell is suitable for tasks that are normally relegated to shell scripts.</p> <p>Recently, I was asked by a colleague to come up with a simple shell script to rename some files. Basically, the files were being moved from a windows machine to a *nix environment, which meant that case sensitivity was going to become an issue. The request was simple enough, we needed to rename all the specified items to lowercase names</p> <p>You might be thinking, ``classic <strong>tr</strong> territory here.'' And, of course, you'd be absolutely correct in going in this direction. The important bits go something like this:</p> <pre><code>for file in ${*}; do downcase=$(echo ${file} | tr 'A-Z' 'a-z') if ! [[ "${downcase}" == "${file}" ]]; fi echo "Moving ${file} to ${downcase}" mv ${file} ${downcase} fi done</code></pre> <p>So what's wrong with that?</p> <p>Absolutely nothing. It's both simple and effective. So why bother writing it in any other language, least of all Haskell? Because it's not beautiful.<p> <p>The great thing about the above code is that you don't really need to put it into a script. You can just bang away at it from the command line -- yes, you can type loops on the command line, <strong>*smirks*</strong>.</p> <p>When at the command line, however, you just do the simplest thing that works: you aren't worried about wrapping the variable names in curly brackets, and so on. <em>IF</em> you do want to keep scripts like this around though, after a while you'll start to see them grow ugly. Nobody likes ugly code, so lets see how it looks if we port it to <a href="http://www.haskell.org/">Haskell</a>.</p> <p>First lets start by importing a few functions:</p> <pre><code>module Main (main) where import System(getArgs) -- so we can get the file names import Data.Char(toLower) -- will do 'tr's work import Directory(renameFile) -- so we can do the renames import Control.Monad -- helps us quit on bad params</code></pre> <p>Those will definitely come in handy later. Okay, so let's talk about the data for this <em>``application.''</em> Obviously we're going to have a bunch of filepaths, but we also need to keep track of the downcased names as well. We'll gather these together with the original names in pairs. Since there will be multiple such pairings, they will be gathered together into a list, which looks like this: <code>[(String,String)]</code>. <p>There are two actions to be done for each pair,</p> <ol> <li>Each pair of directories should be printed to the screen, and</li> <li>The file should be moved from the first name to the second.</li> </ol> <p>Let's define a function to print let the user know what's going on. This is easy enough, and as the type of this function suggests we take a pair and do some IO:</p> <pre><code>putDirs :: (String,String) -> IO () putDirs pair = putStrLn $ "Moving: " ++ fst pair ++ " " ++ snd pair</code></pre> <p>The second action is so simple that we won't even make a function for it. It's by-and-large already done for us -- remember the import from the <code>Directory</code> module?<p> <p>One other thing the original bash version did is check that the file actually has to be moved. This is simple in Haskell. We'll use the higher order function <code>filter</code> to remove the pairs where the two elements are already equal:</p> <pre><code>rmDups = filter (uncurry (/=))</code></pre> <p>Here <code>uncurry</code> takes a function and returns a function which acts on a pair.<p> <p>Now all that is left is to do the main part. Check the parameters and loop our actions over each of the files</p> <pre><code>main = do files <- getArgs let usage = "Usage: dcfiles [files]" when (length files < 1) $ putStrLn ("Argument Error: no arguments specified\n" ++ usage) >> return () let pairs = (rmDups.zip files) $ map (map toLower) files mapM_ (liftM2 (>>) putDirs $ uncurry renameFile) pairs</code></pre> <p>I think all of this reads very well. The only tricky part is the last two lines. A lot of stuff is going on in a little amount of space, but it's still not too hard. <code>toLower</code> takes a character and returns it's lowercase equivalent, so this just get's mapped over the entire string. We do this for each of the file names passed in. Next we use <code>zip</code> to form our pairs from the original list and the mapping, and finally we filter it using our <code>removeDups</code> function.</p> <p>The ultimate line is just a fancy way of saying that we need to map our two actions one after the other. The <code>mapM_</code> construct just says that we'll be mapping actions rather than regular functions.</p> <p>So, now that that's done, what have we gained? We'll if we never added onto the script, it probably would have been just as well to leave it alone. However, if we wanted to make it a little more robust it would be very easy to do and you'd have all the power of Haskell at your disposal.</p> <p>What I wanted to show here is that Haskell needn't be reserved for massively complex projects. It doesn't get any more simple than this. Don't fear Haskell, love it :)</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-513907570507134022?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-49908052890829491932006-04-26T21:52:00.000-05:002007-02-02T17:57:49.398-05:00Missing Perl Modules<p>Hey everybody. Sorry It's been so long since an update. I've had <a href="http://www.elderscrolls.com/">Oblivion</a> taking up all my time of late. It's a great game; check it out if you get the chance&mdash;but that is really neither here nor there. This post is about perl.</p> <p>Soon I'll be moving to a new position at work. I'm going to be taking a break from system administration for a bit and doing some coding, which is really a breath of fresh air. You know, it's the whole problem of dealing with users: they drive you crazy, but they also provide you with a job.</p> <p>Anyway, the new projects I'll be working on will be web stuff, mostly in perl. So, I figured I'd lay off the <em>ruby</em> for a while and get back on the camel. I started out looking at a simple script to do ldap lookups for co-workers. We do have the capability to look up phone extentions using our VoIP enabled telephone sets, but it can really be a pain to key in the information.</p> <p>It all comes down to the interface: I will never be able to key in a query against somebodies last name on the phone as fast as I can on the keyboard&mdash;even if I could remember which letter mapped to which number, you are still required to wait for a timeout between two characters. Otherwise, there would be no way to differentiate between entering 22 meaning "aa" or 22 meaning "b".</p> <p>So the script is really simple, it just takes any number of query strings as command line arguments and plugs them into the ldap query filter using the <strong>Net::LDAP</strong> module. As it stands, everything worked fine on my workstation because I have a whole slew of useful perl modules installed there from the <a href="http://www.cpan.org">CPAN</a>. Unfortunately, the other machines are missing a lot of these modules. I could have installed the missing modules locally on each machine, but that would have taken more effort than it would be worth.</p> <p>Rather than go at it that way, I decided to just work around the problem. The missing functions were some of the really nice list processing routines from <strong>List::MoreUtils</strong>. We'll look at the function <code>all()</code> as an example. <code>all()</code> takes a reference to a function and a list of values. It uses the function as a <em>predicate</em>, returning true only when the function is true for each value in the list. Here is an example of it's usage:</p> <pre><code>if (all { $_ >= 0 } (1,5,4,0,3)) { print "The list contains all positive values.\n"; } else { print "The list has at least one negative value.\n"; }</code></pre> <p>which of course would execute the first branch because they are all positive. It turns out that this function is pretty simple to write in perl, but there are some nice points to using the module version. First the standard reason, you don't have to reinvent the wheel. There's less chance that the module version has bugs in it then our hand rolled version. Second, the module version is written as a <em>C</em> extention, so it's really fast.</p> <p>In the script we want to load the module if it is available, but if we do that, perl will not be able to find it in the include path (@INC) and the program will blow up. What we need to do is <em>require</em> the module file instead. <code>require</code> will throw an execption if the module is not found, and it also does not call the modules import function&mdash;we're going to want to do the import manually anyway as you'll see in a moment.</p> <p>To catch the error, you can use an <em>eval</em> block. If an exception is thrown, it will get stuffed into the package variable <code>$@</code>. We can test this variable to see if the module couldn't be loaded. If not, we define the function ourselves. Otherwise, we just import the modules version into our current namespace. This looks something like the following:</p> <pre><code>BEGIN { no strict 'refs'; eval { require List::MoreUtils }; if ($@) { *all = sub (&@) { my $func_ref = shift; my @items = @_; foreach (@items) { return unless $func_ref->(); } return 1; } } else { *all = \&List::MoreUtils::all } }</code></pre> <p>We put this inside of a <code>BEGIN</code> block because we want this to happen at compile time rather than runtime. The <code>(&@)</code> part after the sub definition is a function prototype. It tells the compiler that we want to look for a subroutine reference as the first parameter, which is what gives us a calling interface like the builtins <code>grep</code> and <code>map</code>.</p> <p>The only other tricky part in there is the assignment to the all typeglob (<code>*all</code>). Since we are setting the value to a funciton reference, the <code>all()</code> function in the current namespace gets set to that function. Now when we call <code>all()</code> in the program we should get either the module version or the one we defined depending on whether it could find the module. If I run it on my workstation it uses the <strong>List::MoreUtils</strong> version. On the other machines, however, I don't have to worry about it not being available. The script will still run, It will just use our above definition instead.</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-4990805289082949193?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-54372783577664343602006-03-16T06:15:00.000-05:002007-02-02T17:59:19.082-05:00Appriciate Your GNU Tools<p>It is often remarked <em>that one never knows what one has until it is lost.</em> While I think that most people do believe something along those lines, I suspect that such feelings are normally reserved for the big things in life: security, companionship, et cetra.<p> <p>Personally, get this feeling every time I log onto a proprietary <strong>UNIX</strong> machine. Perhaps you recognize the situation: for every one of your clever little shell incantations, the machine responds with a brutally cold, <em>"illegal option."</em> I know that this command worked yesterday!" you say to yourself. <p>And then it dawns on you. You were on the <strong>Linux</strong> box yesterday.</p> <p>These are the times that make us realize just how good we have it. Not only have the fine people at the <a href="http://www.fsf.org/"><abbr title="Free Software Foundation">FSF</abbr></a> provided us with tools which are free--as in software--but also tools which are superior with an incredible amount of <em>robustness and functionality</em>. <p>Case in point: Today I was writing a script for our users to generate public keys for use with <code>ssh</code> (<code>scp</code>). The <code>ssh</code> implementation on <a href="http://h30097.www3.hp.com/" title="Tru64 Unix">Tru64</a> uses a different key format than <a href="http://www.openssh.com/" title="Open SSH">OpenSSH</a>. Fine, no big deal. That's not where the problem was.</p> <p>The problem was with humble little <code>grep</code>. I wanted to capture the name of the key created from the <code>ssh-keygen</code> command. With the GNU version it's easy to pick out a just a matching pattern, rather than the entire line: just use the <code>-o</code> option:</p> <pre><code>$ grep -o 'pattern' looking for a pattern <em>&gt; pattern</em> find the pattern in the line <em>&gt; pattern</em> many little patterns. find all of the patterns <em>&gt; pattern &gt; pattern</em> </code></pre> <p>On the Tru64 version, there's no such option. In fact, the number of available options (17) falls far short of those made available by the GNU version (41). I'm not going to argue whether all of those options are necessary, but you'll be happy they are there when you need them most.</p> <p>Luckily <a href="http://www.perl.org/">perl</a> is installed on this machine, so I was still able to solve the problem:</p> <pre><code>$ perl -ne '/id_dsa_\d+_[^.]+/ && print $&, qq/\n/'</code></pre> <p>Of course, this solution presents its own difficulties. Namely, you might be writing a script that needs to be maintained by someone that's taken a few Korn Shell classes, yet knows absolutly zero perl. Besides, this is only an example. Sometimes the workaround doesn't come so easily. (<em>Not to mention that perl is under an <a href="http://www.opensource.org/licenses/artistic-license.php">open source license</a> as well.</em>) </p> <p>So the moral of the story? Be greatful for what you have: especially fantastic software, provided to you for free by volunteers.</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-5437278357766434360?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-14184653438740890012006-03-13T17:45:00.000-05:002007-02-02T17:47:24.773-05:00RPMs by Hand: Ouch!<p>Watching <a href="http://www.debian.org">Debian's</a> Advanced Package Tool (<abbr title="Advance Package Tool">apt</abbr>) at work is an awesome thing to behold. Armed with <code>apt-get</code> and friends, installing a new package is only ever a few keystrokes away:</p> <pre><code># apt-get install <em>mypackage</em></code></pre> <p>The above command will have <abbr title="Advanced Package Tool">apt</abbr> happily run off, fetch the package from the repository and install it on the system. All the dependancy checking is taken care of--for the most part--automatically. But what does this have to do with the <a href="http://www.redhat.com">Redhat</a> Package Manager (<abbr title="Redhat Package Manager">RPM</abbr>)? Well, nothing actually.</p> <p><abbr title="Redhat Package Manager">RPM</abbr> does not intend to solve the same problem as <abbr title="Advanced Package Tool">apt</abbr>. It's actually analogous to dpkg, Debian's package format and associated tools. Therefore, you can't really fault <abbr title="Redhat Package Manager">rpm</abbr> for the fact that</p> <pre><code># rpm -i <em>mypackage.rpm</em></code></pre> <p>will most likely come screaming back at you that you are missing a bunch of dependancies.</p> <p>No. The gripe here is that you really need to have something like apt. If you have a system decended from Debian, based on dpkg, you'll most certainly have <abbr title="Advanced Packaging Tool">apt</abbr> as well; this is not necessarily the case with some RedHat relatives. Many of the <abbr title="Redhat Package Manager">rpm</abbr> based distros do in fact have an acceptable replacement, while others--*hack* *cough* <a href="http://www.novell.com/linux/suse">SuSE</a>--do not.</p> <p>No. YaST does not count. I am (<em>unfairly?</em>) limiting the field to command line applications. There is no way I'm firing up cruddy old yast just to install a simple package.</p> <p>This combined by the fact that that you have packages which depend not only on other packages, but also individual files, leaves you in a sticky situation. Thus you are forced to do something ugly and inefficient like the following:</p> <pre><code>$ find /path/to/rpms -name '*.rpm' | while read package do rpm -qpl "${package}" | while read file; do echo -e "${file}\t${package}" done done > files-per-package $ grep 'somestupidlib.so' files-per-package<code></pre> <p>to find out what you actually need to install. Once you get that list you can go right back to it with</p> <pre><code># rpm -i <strong>some-other-package-with-1x10^7-deps.rpm</strong></code></pre> <p>fun no?</p> <p>P.S. If any SuSE fans out there have a better way of doing this, I'd love to hear it. The Administration Guide provided with SuSE Linux Pro 9.1&mdash;I don't have the more recent ones handy&mdash;suggests doing something remarkably similar to the above.</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-1418465343874089001?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-63088384935433125592006-03-05T16:54:00.000-05:002007-02-02T17:31:02.569-05:00It's All in the Shell<p>Shell scripts are supposed to make our lives easier, right? Scribble down a bit of bash magic and all your busy work will vanish faster than you can type <code>#!/bin/bash</code>. Too good to be true?</p> <p>Well, perhaps, but it will make life much easier in the long run. As your scripts become more advanced, you'll spend <strong>less time doing boring repetitive tasks</strong>, and more time...eh, writing scripts.</p> <p>The best way to start out is to just play around at the command line for a while. This will get you on your way, but here is a pair of tips that will help you train your scripting muscle:</p> <ul> <li><p><strong>Read a man page every day.</strong> The man pages are there to help you, but you can't take advantage of them unless you read them. You'll thank yourself later.</p> <p>Seriously. I'm not kidding. Do it for pleasure. Do it for power. Do it for the babes. Let's face it, ladies dig geeks, and this command that will lead you to everlasting glory:</p> <pre><code>man $(ls -1 /usr/share/man/man1 | \ sed -nr "$(($RANDOM % $(ls /usr/share/man/man1/ | wc -l) +1 ))p" | \ sed "s/\.1\.gz//")</code></pre> <p>What the heck is that, you ask? That will give you the low-down on a random command. Just think of it as "word of the day" for geeks.</p></li> <li><p><strong>Read other quality scripts</strong>. There are plenty of places you can look for them, but some good starting points are the scripts provided by your distribution (You are running an <em>open source</em> operating system, aren't you?). Try looking through the system startup scripts under <code>/etc/init.d/</code>. You can learn a lot of good tricks just by trying to follow along with the code.</p> </li> </ul><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-6308838493543312559?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0tag:blogger.com,1999:blog-6750681900166926642.post-2601692099520263782006-03-04T20:13:00.000-05:002007-02-02T17:34:51.289-05:00"Stupid" SSH Tricks<p>SSH is typically used to log into a remote machine and start up a shell session. This lets users run command line programs just as if they were sitting at a local terminal. That is a very useful ability to have in and of itself. However, some of the coolest things you can do with SSH don't involve starting an interactive session.</p> <p>Here's a shortened version of the options ssh takes:</p> <pre class="command-line">localhost$ ssh [misc. options] [user@]host [commands]</pre> <p>Normally, you'll see something simple along the lines of</p> <pre class="command-line">localhost$ ssh remotehost</pre> <p>but, you can tack on an arbitrary command afterward to run it remotely. Let's say you need to get a list of all the users on a particular machine; there's no need to pull up a remote bash session. Just run this off:</p> <pre class="command-line">localhost$ ssh -x remotehost 'cut -d: -f1 /etc/passwd'</pre> <pre class="command-line-response"> ... gandalf frodo ... localhost$</pre> <p>As indicated in the last line, this will leave you in your original session on the local machine. The <code>-x</code> option is used to turn of x forwarding; this will speed things up significantly.</p> <p>Just using this method opens up a lot of options. If you have your authentication keys set up correctly, you can start running commands like this in shell scripts as well.</p> <p>Also, you can poll many machines all at once. Let's say you want to check your group memberships on a bunch of machines. Use your shell's looping mechanism to do the trick.</p> <pre class="command-line">localhost$ for box in host0{1,2,3,4}; do ssh -x ${box} groups done</pre> <p>Nice.</p> <p><strong>Bonus stupid trick</strong>: if you want to run a remote command that requires user interaction&mdash;perhaps an ncurses application&mdash;you can have ssh request a pseudo terminal. For instance, you can open an editor on a remote file thus:</p> <pre class="command-line">localhost$ ssh -xt vi somefile.txt</pre> <p>I'm sure you can come up with more interesting examples.</p><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6750681900166926642-260169209952026378?l=blog.ljstech.net'/></div>Louhttp://www.blogger.com/profile/09640834186759359236noreply@blogger.com0