commandline.org.uk feedhttp://commandline.org.uk/commandline.org.uk posts feed.en-gbWed, 14 May 2008 10:14:58 -0000Swap out your ssh keyshttp://commandline.org.uk/linux/2008/may/14/swap-out-your-ssh-keys/ <p>You are using the old Feed URL. Please update to <a class="reference" href="http://commandline.org.uk/feeds/full/">http://commandline.org.uk/feeds/full/</a> as soon as possible.</p> <p><strong>Debian and Ubuntu are not random enough</strong></p> <p>There is has been a bug in random number generator on Debian (from Etch onwards) or Ubuntu (Feisty onwards). You should already have a security update for the number generator. If you have not yet accepted the update then do so.</p> <p>If you are on an operating system that has apt-get then you probably want to look at what is going on. If you are on Gentoo or another distribution then for now you can just smile quietly to yourself.</p> <p>In theory, bugs in the number generator are bad mojo because there are less combinations, depending on the severity of the bug, it makes a brute-force attack go from almost completely impossible, to either still very improbable down to theoretically possible with a supercomputer. SSH is often the first point of entry to a Linux machine (but not the last line of defense) so bugs here are particularly prominent.</p> <p>However, lets not have a panic attack about it. There are a dozen easier ways to get into someone's machine. In proprietary software land, they probably would have just ignored this kind of theoretical exploit to keep their marketing team happy. For a proprietary software company, still existing in five years time is a higher priority than a theoretical brute-force attack using hardware of the future. Free/Open Source Software forces good security, your dirty laundry is washed in public. Today's theoretically possible attacks are tomorrow's malware. If we ignore all these things then we end up with an operating system akin to Windows.</p> <p>If you are on Debian or Ubuntu, the security updates means that any new keys will be to the desired level of randomness, but your existing ones need to be ditched. The update manager does not do this for you in case you are then left unable to log into remote systems.</p> <p><strong>Swapping out your SSH keys</strong></p> <p>Cleaning this up is easy. Run:</p> <p><tt class="docutils literal"><span class="pre">sudo</span> <span class="pre">ssh-vulnkey</span> <span class="pre">-a</span></tt></p> <p>This outputs a line for each SSH key on your system:</p> <div class="highlight"><pre>Not blacklisted: 2048 &lt;key fingerprint&gt; &lt;filename&gt; Not blacklisted: 1024 &lt;key fingerprint&gt; &lt;filename&gt; COMPROMISED: 2048 &lt;key fingerprint&gt; &lt;filename&gt; COMPROMISED: 2048 &lt;key fingerprint&gt; &lt;filename&gt; Not blacklisted: 2048 &lt;key fingerprint&gt; &lt;filename&gt; </pre></div> <p>So the ones that came from Gentoo or another Linux distribution are okay as far as we know. The two Ubuntu ones we must delete or archive somewhere else. To delete the keys use rm.</p> <p>Now we might like to generate replacements, so we can still use SSH as before:</p> <p><tt class="docutils literal"><span class="pre">ssh-keygen</span></tt></p> <p>So to make this simpler, one of the lines was: COMPROMISED: 2048 49:37:38:f4:86:28:ac:b1:7e:a6:df:bd:1d:a4:da:81 /home/warrior/.ssh/id_rsa.pub</p> <p>That is the public key of the local machine. So we get rid of it:</p> <p><tt class="docutils literal"><span class="pre">rm</span> <span class="pre">/home/warrior/.ssh/id_rsa</span></tt> <tt class="docutils literal"><span class="pre">rm</span> <span class="pre">/home/warrior/.ssh/id_rsa.pub</span></tt></p> <p>Now we want a new one:</p> <div class="highlight"><pre><span class="nv">$ </span>ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key <span class="o">(</span>/home/warrior/.ssh/id_rsa<span class="o">)</span>: </pre></div> <p>The brackets mean that is the default, so I press enter.</p> <p>Next it finds an existing key (the private half of the existing keypair);</p> <div class="highlight"><pre>/home/warrior/.ssh/id_rsa already exists. Overwrite <span class="o">(</span>y/n<span class="o">)</span>? </pre></div> <p>We want to overwrite it so we say we yes.</p> <div class="highlight"><pre>Enter passphrase <span class="o">(</span>empty <span class="k">for </span>no passphrase<span class="o">)</span>: Enter same passphrase again: Your identification has been saved in /home/warrior/.ssh/id_rsa. Your public key has been saved in /home/warrior/.ssh/id_rsa.pub. </pre></div> <p>Now we are done.</p> <a class="reference" href="http://commandline.org.uk//linux/2008/may/14/swap-out-your-ssh-keys/#discussion">Discuss this post - Leave a comment</a> Wed, 14 May 2008 10:14:58 -0000http://commandline.org.uk/linux/2008/may/14/swap-out-your-ssh-keys/New RSS feed, please update now!http://commandline.org.uk/more/2008/may/13/new-rss-feed-please-update-now/ <p>If you have signed up to my site's RSS feed, please update to <a class="reference" href="http://commandline.org.uk/feeds/full/">http://commandline.org.uk/feeds/full/</a> as soon as possible. Then you will be sure not to miss any of my exciting adventures.</p> <p>This main feed should work the same as the old one, however, I have provided a number of extra feed <a class="reference" href="http://commandline.org.uk/feeds/">options</a>. if that is your bag.</p> <p><em>Update: I am hoping all the old feeds should do something now.</em></p> <a class="reference" href="http://commandline.org.uk//more/2008/may/13/new-rss-feed-please-update-now/#discussion">Discuss this post - Leave a comment</a> Tue, 13 May 2008 00:12:17 -0000http://commandline.org.uk/more/2008/may/13/new-rss-feed-please-update-now/How not to program WSGIhttp://commandline.org.uk/python/2008/may/12/how-not-program-wsgi/ <p><strong>or how not to serve robots.txt with PyBlosxom</strong></p> <p>So as you may have noticed, I moved this site from PyBlosxom to Django, which depending on your perspective is a fabulous thing to do or is tantamount to treason on the high seas. I will explain more about that later.</p> <p>Old links to the site should, in the main, still work hopefully as I have done some regular expressions jujitsu which should hopefully send everyone to where they were supposed to be going.</p> <p>However, some posts and comments will have their formatting up the creek. So I want the old version of the site to be available (at archive.commandline.org.uk) for a while longer.</p> <p>Because the archived version is deprecated and on the way out, I do not want the search engines to index it. Therefore I needed to make a robots.txt file for that subdomain excluding them from indexing it.</p> <p>The last version of this site, like many dynamic sites, is composed of a number of layers, part of which was a lot of my own nonsense code doing various things. Ignoring that, when a request for a packet came in it would go to WSGI which would then pass the request on to PyBlosxom which was at the bottom of it all doing the hard work.</p> <p>To deploy it properly, one would normally put Apache at the front as well, but I never got around to that. In theory this is a bad thing to do. But in practice it worked really well without the huge and complicated server that is Apache in the mix. It actually ran fine for a year without stopping, and blazing fast too; if it also confused a few comment spam bots then all the merrier.</p> <p>So I tried putting Apache into the mix so I could use a Location directive to direct /robots.txt to somewhere with the robots.txt file, but no joy, this would have required doing a lot of what I never got around to before.</p> <p>So I then looked into how the test server was deploying the site, thinking that I could do some kind of smart regular expressions type matching like in Django or Pylons. But nope.</p> <p><strong>Hack for the win</strong></p> <p>So the next step down is PyBlosxom, so I looked out of chance in Pyblosxom/pyblosxom.py and saw the following:</p> <div class="highlight"><pre><span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">env</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;</span> <span class="sd"> Runs the WSGI app.</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="c"># ensure that PATH_INFO exists. a few plugins break if this is</span> <span class="c"># missing.</span> <span class="k">if</span> <span class="s">&quot;PATH_INFO&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">env</span><span class="p">:</span> <span class="n">env</span><span class="p">[</span><span class="s">&quot;PATH_INFO&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s">&quot;&quot;</span> <span class="n">p</span> <span class="o">=</span> <span class="n">PyBlosxom</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="p">,</span> <span class="n">env</span><span class="p">)</span> <span class="n">p</span><span class="o">.</span><span class="n">run</span><span class="p">()</span> <span class="n">pyresponse</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">getResponse</span><span class="p">()</span> <span class="n">start_response</span><span class="p">(</span><span class="n">pyresponse</span><span class="o">.</span><span class="n">status</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">pyresponse</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">items</span><span class="p">()))</span> <span class="n">pyresponse</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span> <span class="k">return</span> <span class="p">[</span><span class="n">pyresponse</span><span class="o">.</span><span class="n">read</span><span class="p">()]</span> </pre></div> <p>Bingo! As soon as I saw it, I just somehow, on auto pilot, typed in the following lines before the line p = PyBlosxom(self.config, env):</p> <div class="highlight"><pre><span class="k">if</span> <span class="n">env</span><span class="p">[</span><span class="s">&quot;PATH_INFO&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="s">&quot;/robots.txt&quot;</span><span class="p">:</span> <span class="n">start_response</span><span class="p">(</span><span class="s">&#39;200 OK&#39;</span><span class="p">,</span> <span class="p">[(</span><span class="s">&#39;Content-type&#39;</span><span class="p">,</span><span class="s">&#39;text/plain&#39;</span><span class="p">)])</span> <span class="k">return</span> <span class="p">[</span><span class="s">&quot;&quot;&quot;User-agent: * </span><span class="se">\n</span><span class="s">Disallow: /&quot;&quot;&quot;</span><span class="p">]</span> </pre></div> <p>And unbelievably it worked. What I had subconsciously done was to see that we have some kind of string referred to by env[&quot;PATH_INFO&quot;]. Then further on we have an object called start_response which is being passed a status and some headers. Then we are returning the response.</p> <p>I was kidding around more than anything so I just replaced everything I didn't know about with reasonable looking constants (you will know these well if you have ever done Python CGI programming).</p> <p>I am sure there are millions of far better ways to serve robots.txt with PyBlosxom. But this hack works for me until I no longer need the old site anymore.</p> <a class="reference" href="http://commandline.org.uk//python/2008/may/12/how-not-program-wsgi/#discussion">Discuss this post - Leave a comment</a> Mon, 12 May 2008 22:54:48 -0000http://commandline.org.uk/python/2008/may/12/how-not-program-wsgi/Give Linux a chancehttp://commandline.org.uk/linux/2008/may/12/give-linux-chance/ <p><strong>Mughlai and Jalfrezi are better than gruel</strong></p> <p>A few hundred years ago, the great mass of the British poor ate gruel, while the middle class ate bland over-boiled vegetables. However, as a naval people, the British went out around the world with their empire, and brought food and foreign chefs back with them. Now British people can and do eat food originating from the whole world. Not just the rich, normal working class people will regularly eat curry, Cantonese food, kebabs and so on that would not have been imaginable before.</p> <p>If I tried to explain this to a 14th Century peasant eating his gruel, then he probably would just ignore me, having no idea what I am talking about. At best he might look at me strangely, and then go back to his life of gruel.</p> <p>Windows is the gruel of the digital world. There are certain people that understand this fact and have moved on to greater and better things, however most people take what they are given and swallow it as best they can.</p> <p><strong>What is an operating system?</strong></p> <p>If you don't know what a computer program is, think about cooking. In cooking you have tools, such as an oven and a blender and you have ingredients such as vegetables and meat. The recipe allows you to use the tools to take your food (ingredients) and turn them into to other types of food (meals).</p> <p>In computers, you have tools (called hardware) such as a DVD player, a screen, a keyboard, a hard drive and so on. The computer program is the mathematical recipe that allows you to take your data (text, pictures, videos, etc) and then do various things with it. So for example, you might use a computer program that takes a song from the hard-drive and then plays it out of the speakers.</p> <p>An operating system is a set of computer programs that makes your computer hardware do the basic things (put text on the screen, play sound, and so on). You might then run other programs to do more advanced things. It is like your recipe book.</p> <p>People who are in to cooking will try out lots of recipe books, and in doing so, they do not starve because they have bought a different recipe book, indeed the opposite happens, they cook so much that they do not have time to eat it all themselves so have to give food to their family and neighbours.</p> <p>By changing recipe book, they don't suddenly become unable to cook, they in fact get better as they move on to better and more advanced recipe books.</p> <p>If you turn on you computer and see Windows, then Windows is your current 'operating system'. If you have only ever used Windows, don't you think it is time to give up the gruel and try a new recipe book?</p> <p>I think so, and if you think this way then you have come to the right place!</p> <p><strong>About Windows</strong></p> <p>Windows started on home computers and was commonly used for playing computer games. Though some shifty business deals in the 1980s and 1990s, Windows became pre-installed on the PC and so became the main operating system used by non-technical users.</p> <p>However, there a lot of people that think that this situation is not good for humanity and we need to progress past it.</p> <p>Why? Well lets look at some of the reasons.</p> <p>Firstly, Windows does not promote a competitive industry. Only Microsoft can sell Windows, only Microsoft can really provide complete support for Windows. Mainstream PC shops may only stock Windows PCs.</p> <p>So you have one company earning billions of their monopoly, with these excess profits, Microsoft can then give campaign contributions to politicians to make sure they don't make the industry competitive or hold Microsoft to account when they break the law.</p> <p>Weak, bribed, politicians allow Microsoft to use the educational system as a giant marketing tool, indoctrinating a new generation to become helpless and passive recipients of Microsoft's, and only Microsoft's, products.</p> <p>Secondly, each version of Windows is developed in secret, and then launched with billions of dollars worth of marketing to make you believe the magic; however like all magic, it is no replacement for public peer review. Microsoft don't like public peer review because they know that when compared fairly to other operating systems, Windows always loses.</p> <p>The fact that there is no public peer review, and no effective competitive pressures, means that Windows is not very well engineered. When the main architecture of DOS and Windows was created in the 80s, it was already 20 years behind the state of computer science; and it has not really changed that much since.</p> <p>This 'closed-off from the world in my own cave' approach to software engineering means that Windows is plagued with security problems, it uses computer resources inefficiently, wasting electricity and requiring unnecessary replacement of perfectly fine computers that could have lasted another five to ten years.</p> <p>Fourthly, a software mono-culture, like a biological monoculture, is not very healthy. If a future Windows virus wipes out all of the world's Windows PCs, then 90% of the computer using population are offline, without their data and without access to government services, Internet commerce and digital information. Businesses would collapse and the western world would be plunged into a digital dark age.</p> <p>Fourthly, because what Windows is doing is a secret; if you use Windows, then you are not in control of your computer, Microsoft is. Windows reports back lots of data to the USA which is then made available to whomever Microsoft wants to share it with. While most of us are not interesting to the US security agencies; Microsoft can sell your private information to anyone.</p> <p>In short, Windows leaves your backdoor open to Microsoft, but even if you trust Microsoft, the US government, and all companies that Microsoft might sell your information too; the fact there are built-in backdoors means that anyone, criminals, terrorists, anyone, can potentially walk through Microsoft's backdoor to access your private data or install viruses or tracking software on your PC.</p> <p><strong>There is another way...</strong></p> <p>...Indeed there are lots of them! The opposite to Windows slavery is software freedom. And with freedom comes lots of choices, and choices are good! If you have spent a lifetime eating gruel then you might resent choice, but then remember the intolerant character in Dr Seuss' classic &quot;Green Eggs and Ham&quot;, who resists and resists trying out new things for unjustified reasons.</p> <p>The operating system I currently use is GNU/Linux (commonly just called Linux), which started out in Universities and parts were contributed by thousands of volunteers over the world wide web; others soon joined in, such as small and large companies, charities and even the American military.</p> <p>Unlike Windows, any company or individual can share, sell, give away or provide services for GNU/Linux, anyone can change it, and there is complete public peer review. There are no hidden traps and you are in control of your own computer.</p> <p>Also the way Linux executes programs is based on a completely different architecture. There is no concept of an untrusted, unknown program having access to everything. The problems that plague Windows, viruses, spywhere, malware and worms, do not exist in the Linux world. They have never existed and will never exist, because the architecture of the system is not designed that way.</p> <p>So as I said before, anyone can give out Linux, so lots of people do (remember: choice is good) most versions are free and you can legally share them with your friends and neighbours without having to ask anyone.</p> <p>Give it a go!</p> <a class="reference" href="http://commandline.org.uk//linux/2008/may/12/give-linux-chance/#discussion">Discuss this post - Leave a comment</a> Mon, 12 May 2008 11:00:00 -0000http://commandline.org.uk/linux/2008/may/12/give-linux-chance/Email Syntax Check in Pythonhttp://commandline.org.uk/python/2008/may/3/email-syntax-check/ <p>Sometimes you may want to check that an email address is not syntactically invalid, i.e. it looks like a recognisable email address. I use this approach in my zetact contact form processor.</p> <p>Of course, it does not mean the address actually leads anywhere, but at least you know are dealing with an email address that could exist.</p> <p>This is the code I have been using, albeit I have changed it from a class method to a simple function to make this post simpler.</p> <div class="highlight"><pre><span class="sd">&quot;&quot;&quot;Email check using regex.&quot;&quot;&quot;</span> <span class="k">def</span> <span class="nf">invalidreg</span><span class="p">(</span><span class="n">emailkey</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Email validation, checks for syntactically invalid email</span> <span class="sd"> courtesy of Mark Nenadov.</span> <span class="sd"> See</span> <span class="sd"> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65215&quot;&quot;&quot;</span> <span class="k">import</span> <span class="nn">re</span> <span class="n">emailregex</span> <span class="o">=</span> <span class="s">&quot;^.+</span><span class="se">\\</span><span class="s">@(</span><span class="se">\\</span><span class="s">[?)[a-zA-Z0-9</span><span class="se">\\</span><span class="s">-</span><span class="se">\\</span><span class="s">.]+</span><span class="se">\\</span><span class="s">.([a-zA-Z]{2,3}|[0-9]{1,3</span><span class="se">\</span> <span class="s"> })(</span><span class="se">\\</span><span class="s">]?)$&quot;</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">emailkey</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mf">7</span><span class="p">:</span> <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">emailregex</span><span class="p">,</span> <span class="n">emailkey</span><span class="p">)</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">:</span> <span class="k">return</span> <span class="bp">False</span> <span class="k">return</span> <span class="bp">True</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> </pre></div> <p>I decided it would be more Pythonic to try to do this using the built-in string methods, rather than importing the re module and using a monster regular expression. Here was my first attempt.</p> <div class="highlight"><pre><span class="sd">&quot;&quot;&quot;Email checks using string methods - simple version.&quot;&quot;&quot;</span> <span class="k">def</span> <span class="nf">invalidemail</span><span class="p">(</span><span class="n">emailaddress</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Checks for a syntactically invalid email address.&quot;&quot;&quot;</span> <span class="k">try</span><span class="p">:</span> <span class="n">emailitems</span> <span class="o">=</span> <span class="n">emailaddress</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s">&#39;@&#39;</span><span class="p">,</span> <span class="mf">1</span><span class="p">)</span> <span class="n">emailitems</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">emailitems</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s">&#39;.&#39;</span><span class="p">,</span> <span class="mf">1</span><span class="p">))</span> <span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="k">if</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">emailitems</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">x</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s">&quot;.&quot;</span><span class="p">,</span><span class="s">&quot;&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">isalnum</span><span class="p">()]</span> \ <span class="ow">and</span> <span class="n">emailaddress</span> <span class="o">&gt;=</span> <span class="mf">7</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="bp">False</span> </pre></div> <p>After a bit of testing and playing with this, a friend pointed me towards the <a class="reference" href="http://tools.ietf.org/html/rfc3696#section-3">relevant RFC</a> on restrictions of email addresses. While the standard allows the use of many different special characters, in practice email addresses have to be much stricter if you actually want people in the real world to be able to send email to you.</p> <p>For example, if we allow the email address []&#64;commandline.org.uk, will whatever receives the output of this function be able to use it? As <a class="reference" href="http://www.regular-expressions.info/email.html">pointed out by Jan Goyvaerts</a>, most software won't actually be able to handle obscure special characters.</p> <p>We also don't want to water down the syntax check and allow junk for the sake of theoretical but non-existent addresses.</p> <p>My compromise is to allow these special symbols -_.%+. in the local-part of the email address, and -_. in the domain name. I also do sanity checking on the top-level domain, it needs to be either a generic name or two characters long (country codes are all two letters).</p> <p>So below is my current version, I added lots of comments and white space to make it easy to read.</p> <div class="highlight"><pre><span class="sd">&quot;&quot;&quot;Ditch nonsense email addresses.&quot;&quot;&quot;</span> <span class="n">GENERIC_DOMAINS</span> <span class="o">=</span> <span class="s">&quot;aero&quot;</span><span class="p">,</span> <span class="s">&quot;asia&quot;</span><span class="p">,</span> <span class="s">&quot;biz&quot;</span><span class="p">,</span> <span class="s">&quot;cat&quot;</span><span class="p">,</span> <span class="s">&quot;com&quot;</span><span class="p">,</span> <span class="s">&quot;coop&quot;</span><span class="p">,</span> \ <span class="s">&quot;edu&quot;</span><span class="p">,</span> <span class="s">&quot;gov&quot;</span><span class="p">,</span> <span class="s">&quot;info&quot;</span><span class="p">,</span> <span class="s">&quot;int&quot;</span><span class="p">,</span> <span class="s">&quot;jobs&quot;</span><span class="p">,</span> <span class="s">&quot;mil&quot;</span><span class="p">,</span> <span class="s">&quot;mobi&quot;</span><span class="p">,</span> <span class="s">&quot;museum&quot;</span><span class="p">,</span> \ <span class="s">&quot;name&quot;</span><span class="p">,</span> <span class="s">&quot;net&quot;</span><span class="p">,</span> <span class="s">&quot;org&quot;</span><span class="p">,</span> <span class="s">&quot;pro&quot;</span><span class="p">,</span> <span class="s">&quot;tel&quot;</span><span class="p">,</span> <span class="s">&quot;travel&quot;</span> <span class="k">def</span> <span class="nf">invalid</span><span class="p">(</span><span class="n">emailaddress</span><span class="p">,</span> <span class="n">domains</span> <span class="o">=</span> <span class="n">GENERIC_DOMAINS</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Checks for a syntactically invalid email address.&quot;&quot;&quot;</span> <span class="c"># Email address must be 7 characters in total.</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">emailaddress</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mf">7</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="c"># Address too short.</span> <span class="c"># Split up email address into parts.</span> <span class="k">try</span><span class="p">:</span> <span class="n">localpart</span><span class="p">,</span> <span class="n">domainname</span> <span class="o">=</span> <span class="n">emailaddress</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s">&#39;@&#39;</span><span class="p">,</span> <span class="mf">1</span><span class="p">)</span> <span class="n">host</span><span class="p">,</span> <span class="n">toplevel</span> <span class="o">=</span> <span class="n">domainname</span><span class="o">.</span><span class="n">rsplit</span><span class="p">(</span><span class="s">&#39;.&#39;</span><span class="p">,</span> <span class="mf">1</span><span class="p">)</span> <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="c"># Address does not have enough parts.</span> <span class="c"># Check for Country code or Generic Domain.</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">toplevel</span><span class="p">)</span> <span class="o">!=</span> <span class="mf">2</span> <span class="ow">and</span> <span class="n">toplevel</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">domains</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="c"># Not a domain name.</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="s">&#39;-_.%+.&#39;</span><span class="p">:</span> <span class="n">localpart</span> <span class="o">=</span> <span class="n">localpart</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="s">&quot;&quot;</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="s">&#39;-_.&#39;</span><span class="p">:</span> <span class="n">host</span> <span class="o">=</span> <span class="n">host</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="s">&quot;&quot;</span><span class="p">)</span> <span class="k">if</span> <span class="n">localpart</span><span class="o">.</span><span class="n">isalnum</span><span class="p">()</span> <span class="ow">and</span> <span class="n">host</span><span class="o">.</span><span class="n">isalnum</span><span class="p">():</span> <span class="k">return</span> <span class="bp">False</span> <span class="c"># Email address is fine.</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="bp">True</span> <span class="c"># Email address has funny characters.</span> <span class="c"># Start the ball rolling.</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span> <span class="k">print</span> <span class="n">invalid</span><span class="p">(</span><span class="s">&quot;warrior@example.com&quot;</span><span class="p">)</span> </pre></div> <p><a class="reference" href="http://commandline.org.uk/python/email-syntax-check-2008-05-03-03-00.html?showcomments=yes#discussion">Discuss this post - Leave a comment</a></p> <a class="reference" href="http://commandline.org.uk//python/2008/may/3/email-syntax-check/#discussion">Discuss this post - Leave a comment</a> Sat, 03 May 2008 02:00:00 -0000http://commandline.org.uk/python/2008/may/3/email-syntax-check/Three more tips - use keybindings, scripts and SSH without passwordshttp://commandline.org.uk/command-line/2008/apr/29/three-command-line-tips/ <p><strong>Use Readline shortcuts</strong></p> <p>At the bash prompt, you can use the default readline keybindings, these are similar to Emacs ones. Many of these are also available within other programs that use readline, such as the Python interpreter.</p> <p>Here are some useful ones:</p> <p>Ctrl-A Beginning of Line</p> <p>Ctrl-E End of Line</p> <p>Ctrl-U Kill (cut) everything left of cursor</p> <p>Ctrl-K Kill (cut) everything right of cursor</p> <p>Ctrl-W Kill (cut) the single word before the cursor</p> <p>Ctrl-Y Yank (paste) the text back</p> <p>Ctrl-L Clear Screen</p> <p>Ctrl-D Exit</p> <p>Ctrl-R Reverse interactive-search, (attempt to complete what is currently being typed using the history file)</p> <p><strong>SSH without Passwords</strong></p> <p>If you login to a remote machine often and you get bored of typing the password, then you can use public key cryptography instead.</p> <p>The way it works is that the remote machine has a copy of your local machine's public key, it can then use that to check that your local machine is really your machine, and so let you in.</p> <p>To start with, on the local machine, see if you already have a key pair:</p> <p><tt class="docutils literal"><span class="pre">ls</span> <span class="pre">~/.ssh/id_?sa.pub</span></tt></p> <p>If not, then make one:</p> <p><tt class="docutils literal"><span class="pre">ssh-keygen</span> <span class="pre">-t</span> <span class="pre">dsa</span></tt></p> <p>Now you need to copy your public key to the remote host. On the local machine run:</p> <p><tt class="docutils literal"><span class="pre">scp</span> <span class="pre">~/.ssh/id_?sa.pub</span> <span class="pre">remotehost:</span></tt></p> <p>Now we login to the remote server:</p> <p><tt class="docutils literal"><span class="pre">ssh</span> <span class="pre">remotehost</span></tt></p> <p>Append the public key to your authorized keys file</p> <p><tt class="docutils literal"><span class="pre">cat</span> <span class="pre">id_?sa.pub</span> <span class="pre">&gt;&gt;</span> <span class="pre">~/.ssh/authorized_keys</span></tt></p> <p>Now you can login without passwords. Make sure the security of your machines is well thought out. Use <a class="reference" href="http://commandline.org.uk/linux/encrypt-for-xmas-2007-12-12-02-00.html?showcomments=yes">disk encyption</a> if possible.</p> <p><strong>Create a script directory in home directory</strong></p> <p>I often talk about random Python or bash scripts. The easy way to use them on Linux is to make a dedicated script directory for these.</p> <p><tt class="docutils literal"><span class="pre">mkdir</span> <span class="pre">~/bin</span></tt></p> <p>Add it to your shell's path. Edit ~/.bashrc and add:</p> <p><tt class="docutils literal"><span class="pre">export</span> <span class="pre">PATH=$HOME/bin:$PATH</span></tt></p> <p>Now all the scripts that you add to ~/bin are always available. This makes things a lot more flexible and fun as you can try out various scripts by dropping them in ~/bin and then deleting them when you are bored of them.</p> <p><a class="reference" href="http://commandline.org.uk/command-line/three-command-line-tips-2008-04-29-22-00.html?showcomments=yes#discussion">Discuss this post - Leave a comment</a></p> <a class="reference" href="http://commandline.org.uk//command-line/2008/apr/29/three-command-line-tips/#discussion">Discuss this post - Leave a comment</a> Tue, 29 Apr 2008 21:00:00 -0000http://commandline.org.uk/command-line/2008/apr/29/three-command-line-tips/Twelve commandments for Beautiful Python codehttp://commandline.org.uk/python/2008/apr/25/twelve-commandments-of-python-style/ <p><strong>Living Code</strong></p> <p>David Parker famously said that texts are living, once they leave the pen of the author then they have a life of their own, you never know where the text will end up or how it will be modified. For Python code that is even more true.</p> <p>The beauty of Python is that you can write code fast, share code and modify code. For this to work, your code needs to be readable. Writing code is easy, reading other people's code is much harder, or even reading your own code after a few months or years has past.</p> <p>Therefore the aim is to make code as readable as possible, even if it causes a little more work when you write it. The way to make your Python code most readable is to keep to the <a class="reference" href="http://www.python.org/dev/peps/pep-0008/">Style Guide for Python Code</a>, also known as PEP8.</p> <p><strong>Pylint for the Win</strong></p> <p>It is far easier to keep your code valid to PEP8 as you go along, than to try to move a large codebase to PEP8 at the end. I recommend the use of a tool called pylint.</p> <p>Pylint is available from all Linux distributions' package managers (e.g. <em>apt-get install pylint</em> or <em>emerge pylint</em>). Here are some <a class="reference" href="http://thinkhole.org/wp/2006/01/16/installing-pylint-on-windows/">instructions for Windows</a>.</p> <p>If you have ever made a webpage you probably know about HTML-tidy or the online W3C Validator tool. These tell you everything wrong with your HTML.</p> <p>Pylint is similar, it goes through and tells you both syntax errors and also how your code differs from the PEP8 standard.</p> <p>There are some corner cases in which you will need to give pylint the finger, but doing it consciously for good reason is better than because you are sloppy.</p> <p><strong>PEP8 is better than your crappy style</strong></p> <p>People often don't use PEP8. This is for a variety of (bad) reasons.</p> <p>Firstly, sometimes people are tourists from another programming language, they do not know any better so they write their Python code like it was Java or C code.</p> <p>Secondly, Sometimes people think their (cl)own style is better than PEP8 in some technical way. Well that does not matter. I might have a better way to design a plug socket, but if I implemented my better plug socket, I would not be able to buy any electrical devices.</p> <p>There can only be one standard, and PEP8 is that standard. If you want to change that standard then bribe, sleep with or kill Guido Van Rossum.</p> <p>Not following the standard makes your code less readable to others, this prevents the quick reuse that Python is designed for (see above).</p> <p>If you are a free-software/open-source project, then you particularly should be ashamed if you write hard to read code, because allowing other people to read, understand and modify your code is the whole point.</p> <p>Lastly, some people don't use PEP8 because the document is too circular and verbose for them to remember. I feel your pain, below are the main points in 12 easy rules.</p> <p><strong>The 12 commandments</strong></p> <p>Guido, who brought you out of the land of Visual Basic, out of the land of slavery, spake all these words to thee:</p> <ul class="simple"> <li>Module names should be in all lowercase - hello.py.</li> <li>Class names should be in CamelCase.</li> <li>Methods and functions should be in lower_with_underscores</li> <li>Implementation-specific 'private' methods _single_underscore_prefix</li> <li>Especially private non-subclassable methods __double_underscore_prefix</li> <li>Top level constants (i.e. those that are not in a function or class) should be in BLOCKCAPITALS. Overuse of these constants may make your code less reusable.</li> <li>If a variable inside a function or method is so temporary and disposable that you cannot give it a name, then use i for the first one, j for the second and k for third.</li> <li>Indentation is four spaces per level. No tabs. If you break this rule then you must be stoned in the village square.</li> <li>Lines are never more that 80 characters wide. Tip, break lines with a backward slash . You do not need to do this if there are parentheses, brackets or braces. Don't add extra parentheses just to break lines, use instead.</li> <li>Spaces after commas, (green, eggs, and, ham)</li> <li>Spaces around operators i = i + 1</li> <li>Write docstrings for all public modules, functions, classes, and methods. Python is an international community, so use English for docstrings, object names and comments. If you want to provide local translations then use a proper localisation library.</li> </ul> <p><a class="reference" href="http://commandline.org.uk/python/twelve-commandments-of-python-style-2008-04-25-19-00.html?showcomments=yes#discussion">Discuss this post - Leave a comment</a></p> <a class="reference" href="http://commandline.org.uk//python/2008/apr/25/twelve-commandments-of-python-style/#discussion">Discuss this post - Leave a comment</a> Fri, 25 Apr 2008 18:00:00 -0000http://commandline.org.uk/python/2008/apr/25/twelve-commandments-of-python-style/Filesharing is the democratic choicehttp://commandline.org.uk/ethics/2008/apr/18/filesharing-has-a-mandate/ <p><strong>Nonsense laws are socially divisive</strong></p> <p>In Saudi Arabia, ownership of a Bible is illegal. If you are found in the possession of a Bible, at the very least you will have it confiscated, you may be given corporal punishment. If you are found in possession of many bibles, then you can be executed (<a class="reference" href="http://www.cnsnews.com/ViewForeignBureaus.asp?Page=/ForeignBureaus/archive/200505/FOR20050519a.html">source</a>).</p> <p>We do not have that law in UK, or any laws like it. Not only is the UK a Christian country, (in name at least), but also because the majority of the people in the UK think that laws against owning books are stupid, only barbarians have such laws. In Britain you can own the Bible, the Koran, Harry Potter, whatever the heck book you want.</p> <p>No matter what legal arguments you make for a law, if the majority of the public think it is stupid then it won't work.</p> <p>A lot of countries used to have laws against &quot;Nightwalking&quot;, i.e. walking around at night, because people out at night are obviously up to no good. Most countries have abolished such laws. Make laws about soliciting maybe, but wandering around at night? That was just silly.</p> <p>Silly laws should be abolished. If you just leave silly laws on the statue book, expecting everyone to just ignore these laws, then you are undermining the law itself and making a mockery of the institutions charged with enforcing the law.</p> <p><strong>How many million people file share?</strong></p> <p>An study in 2005 claimed than 9.2 million people in Britain were involved in filesharing, which represented an annual 50% increase from the 4.3 million people that were involved in filesharing in 2003. <a class="reference" href="http://www.guardian.co.uk/technology/2005/nov/08/news.netmusic">Source 1</a>, <a class="reference" href="http://www.pcpro.co.uk/news/78525/p2p-activity-doubles-in-two-years.html">Source 2</a>.</p> <p>What is the number now? If a 50% annual increase was maintained, then it would have been 13.8 million people in 2006, 20.7 million people in 2007, and 31 million by the end of this year, 2008.</p> <p>By the end of next year, it would be 46.6 million, and by 2010, it would be 70 million. That can't be possible of course as the population of the UK is only <a class="reference" href="http://www.statistics.gov.uk/cci/nugget.asp?ID=6">60.6 Million</a>.</p> <p>So we can be pretty sure the number of people file-sharing is within the range of 10 million to 60.6 million. If you have more accurate figures please let me know. For sake of discussion, lets choose an arbitrary number - 20 million.</p> <p><strong>Filesharing is normal behaviour</strong></p> <p>Lets say that 20 million people in the UK have been or are involved in filesharing. With 20 million people, filesharing is not a crime, it is a mandate.</p> <p>Labour achieved 9.5 million votes at the 2005 general election, for this it received 356 out of 646 possible seats, and became the government.</p> <p>If 20 million people in the UK are filesharing then it cannot be considered a crime, or a bad act, it is the democratic will of the nation.</p> <p>The majority of people have decided that previewing a song by downloading it is fair, the majority of people have decided that sharing a file with your friends is not the same as stealing a car.</p> <p>Therefore, the government must stop trying to make 20 million British people into criminals, but instead should try to understand the cultural changes happening here and then frame the policy agenda accordingly.</p> <p>The public are not interested in the police spending time in a futile mission to stop kids sharing music with other in order to prop up dying foreign companies. Instead spend the scarce resources on stopping organised drug crime, or on solving murders or on confiscating illegal guns from urban street gangs.</p> <p>If the government cannot see this, and wants to waste our money on misadventures, then we will throw you lot out and get a government that does represent our values. That is democracy.</p> <p><strong>The old music companies are not important to the economic future of Britain</strong></p> <p>Likewise, the music and film industries also need to wake up and smell the coffee, their potential customers like sharing music and film on the web with each other. It is too late, they need to just get over it.</p> <p>Suing their own customers is not going to help them manage decline. It is not going to turn back the clock. The old companies that represent yesterday's music industry cannot burn down the Internet, however hard it tries. The Internet has become far bigger and far more important economically and socially than the old music or film companies.</p> <p>Google is one of the biggest web companies, alone it makes $10 billion in annual profit, that is double the profit for the whole music industry. To put it another way, all of these old music companies are worth, economically speaking, half a Google.</p> <p>Things change, that is life, get on with it. The arrival of electric refrigeration killed off the ice storage companies. Tough luck, no one made a law protecting the old ice storage companies against people using freezers in their homes to make ice.</p> <p>These old music and film companies if yesterday must be told to just live with it, get on with it, embrace it. Make services that appeal to these people, or cease to exist. Governments must not kill the Internet golden goose for some old dying companies.</p> <p>The old ice storage companies going to the wall did not end cold drinks in the summer, in fact electronic refrigeration led to a massive increase in the number of cold drinks available.</p> <p>Likewise if the old music companies are too slow to adapt and go to the wall, it will not be the end of music, or the beginning of the end of music, it will be, as Churchill famously said, the end of the beginning.</p> <p><strong>Politicians, this is your final warning</strong></p> <p>If you are a politician, be aware, we are watching you like never before. In the past you might have taken party contributions from special interests and then given them special treatment.</p> <p>Now we, the public, have our own communication channels and this time, we will punish you for it. You will represent us, the people, or we will remove you.</p> <p><a class="reference" href="http://commandline.org.uk/ethics/filesharing-has-a-mandate-2008-04-18-15-00.html?showcomments=yes#discussion">Discuss this post - Leave a comment</a></p> <p><a class="reference" href="http://digg.com/linux_unix/Filesharing_is_the_democratic_choice">Entry at Digg</a></p> <a class="reference" href="http://commandline.org.uk//ethics/2008/apr/18/filesharing-has-a-mandate/#discussion">Discuss this post - Leave a comment</a> Fri, 18 Apr 2008 14:00:00 -0000http://commandline.org.uk/ethics/2008/apr/18/filesharing-has-a-mandate/Linus Torvalds on ...http://commandline.org.uk/more/2008/apr/16/linus-quotes/ <p>Linus Torvalds writes the Linux kernel, he also likes a good mailing list flamewar, not least because he has a very sarcasatic wit. Here he is, writing about various topics.</p> <p>On fair use:</p> <p>&gt; When you start thinking that you have absolute control over the content or programs you produce, and that the rest of the worlds opinions doesn't matter, you're just _wrong_.</p> <p>&gt; Me, personally, I think the RIAA and the MPAA is a shithouse. They are immoral.</p> <p>On virtualization:</p> <p>&gt; I think what you're seeing is virtualization proponents being absolutely _desperate_ for any reason to use virtualization.</p> <p>On userspace binary drivers:</p> <p>&gt; No user-space ass-hattery here.</p> <p>On turning off interrupt requests:</p> <p>&gt; You cannot have a generic kernel driver that doesn't know about the low- level hardware (not with current hardware - you could make the &quot;shut the f*ck up&quot; a generic thing if you designed hardware properly, but that simply does not exist in general right now).</p> <p>On those arguing for userpace interrupt request handlers:</p> <p>&gt; You may be a bit simple. But I think it's more polite to call you &quot;special&quot;. Or maybe just not very used to how hardware works.</p> <p>On C++ :</p> <p>&gt; In fact, in Linux we did try C++ once already, back in 1992. It sucks. Trust me...</p> <p>&gt; C++ is a horrible language. It's made more horrible by the fact that a lot of substandard programmers use it, to the point where it's much much easier to generate total and utter crap with it. Quite frankly, even if the choice of C were to do <em>nothing</em> but keep the C++ programmers out, that in itself would be a huge reason to use C.</p> <p>&gt; So I'm sorry, but for something like git, where efficiency was a primary objective, the &quot;advantages&quot; of C++ is just a huge mistake. The fact that we also piss off people who cannot see that is just a big additional advantage.</p> <p>On Linux Kernel version 2.6.19:</p> <p>&gt; It's one of those rare &quot;perfect&quot; kernels. So if it doesn't happen to compile with your config, you can rest easy knowing that it's all your own d*mn fault, and you should just fix your evil ways.</p> <p>On Intel's inventions:</p> <p>&gt; The fact that ACPI was designed by a group of monkeys high on LSD, and is some of the worst designs in the industry obviously makes running it at _any_ point pretty damn ugly. And the fact that MB vendors don't test it with anything else than Windows (and sometimes you wonder whether they do even that) doesn't help.</p> <p>&gt; EFI is this other Intel brain-damage (the first one being ACPI). It's totally different from a normal BIOS, and was brought on by ia64, which never had a BIOS, of course. Sadly, Apple bought into the whole &quot;BIOS bad, EFI good&quot; hype, so we now have x86 machines with EFI as the native boot protocol.</p> <p>On Apple OS X:</p> <p>&gt; OS X in some ways is actually worse than Windows to program for. Their file system is complete and utter crap, which is scary.</p> <p><a class="reference" href="http://commandline.org.uk/more/linus-quotes-2008-04-16-21-20.html?showcomments=yes#discussion">Discuss this post - Leave a comment</a></p> <a class="reference" href="http://commandline.org.uk//more/2008/apr/16/linus-quotes/#discussion">Discuss this post - Leave a comment</a> Wed, 16 Apr 2008 20:20:00 -0000http://commandline.org.uk/more/2008/apr/16/linus-quotes/Sharing our scripts togetherhttp://commandline.org.uk/python/2008/apr/15/sharing-our-scripts-together/ <p><strong>Diverse technical interests</strong></p> <p>My technical focus has shifted quite a lot over the years.</p> <p>I was really into computers as a small child, but took my teenage years off it to play guitar, bike outside and meet girls. Then I started to get more heavily into computers (again) in 1998 when I went to University and had a broadband connection.</p> <p>I started by copy-editing for the web and writing bucket loads of HTML. This led to ASP and then a bit of PHP. I then, around 2002, moved to open source software and got deeper and deeper into Linux/Unix system administration, as well as, of course, making websites and setting up CMSes and so on.</p> <p>Over the years I had picked up the very basics in many different programming languages, but I decided in 2006 that if I was going to develop my programming (while working and studying part time), then I should concentrate on one language for the moment.</p> <p>I wanted a language that I could use to make interactive websites, as well as use for system administration, but it also needed to be good for programming for the Linux desktop.</p> <p>So in the Summer of 2006, I sat in a relative's garden with a beatup ancient laptop and some printed out Python documentation, and never looked back.</p> <p>These days my favourite computing activity is programming, especially with the Python, as regular readers might have noticed. I am churning out Python at a good rate, some of it for paid work, some of it for my own private learning and a tiny bit ends up in my <a class="reference" href="http://zeth.me.uk/python/">public Python script directory</a>.</p> <p>In the coming months the amount of programming will rise still further as I am likely to take on some new roles from May, more of that will be revealed later when/if that happens.</p> <p><strong>Let's all share our scripts together</strong></p> <p>So I was asked:</p> <p>&gt; zeth have you got a bzr repo of your python scripts? so people using your little apps could give something back?</p> <p>An interesting idea. The whole idea of free software/open source is that people can modify the code and share the changes. It might be that no one ever uses it, if so I have lost nothing, it is worth a try.</p> <p>I only wanted to include the ones I thought were both useful and were relatively self-contained. The focus is on more on developing useful scripts that can be used by others, rather than in old scripts that only fulfilled a need of mine only. So I had a bit of a clear out, and uploaded nine fairly self-contained Python modules to Launchpad. With the idea is that I publish new ones there in the months ahead.</p> <p><a class="reference" href="https://launchpad.net/eden/">Here is the site</a>. I was going to call it &quot;Zeth's scripts&quot; but since the idea is that it will be a collaborative affair, i.e. you can get involved, it is called <a class="reference" href="https://launchpad.net/eden">Eden</a>.</p> <p>It is a garden because it is small now but might develop and change over time. Some scripts will grow, some will die.</p> <p>For example, in the last post here, I made a module which took updates from Twitter and put them in pop-up notifications in the GNOME-desktop. Say someone takes the program and changes it so that Facebook updates are published instead, or makes it so it makes pop-ups for KDE or the Mac instead of for GNOME.</p> <p>That person could then push their version ('branch' in bzr terminology') back up to the site. They do not need permission from me or anyone else. Launchpad can contain as many branches as you like.</p> <p>Even more importantly, other people can also submit their own scripts and we can discuss them and modify them. You don't even have to use Python (shock, horror).</p> <p><strong>Making your own branch</strong></p> <p>So to get your own branch of the programs.</p> <p><tt class="docutils literal"><span class="pre">bzr</span> <span class="pre">branch</span> <span class="pre">lp:eden</span></tt></p> <p>Now lets say you have edited one of the scripts, or you have put new scripts into the directory. You can then share them back to launchpad like this:</p> <p><tt class="docutils literal"><span class="pre">bzr</span> <span class="pre">push</span> <span class="pre">sftp://zeth0&#64;bazaar.launchpad.net/~zeth0/eden/devel</span></tt></p> <p>Replace zeth0 both times with whatever your launchpad username is.</p> <p>If you don't have a launchpad username <a class="reference" href="https://launchpad.net/eden/+login">click here</a> and enter an email address.</p> <p>As that bloke with the beard says, &quot;Happy Hacking&quot;.</p> <p><a class="reference" href="http://commandline.org.uk/python/sharing-our-scripts-together-2008-04-15-00-01.html?showcomments=yes">Discuss this post - leave a comment</a></p> <a class="reference" href="http://commandline.org.uk//python/2008/apr/15/sharing-our-scripts-together/#discussion">Discuss this post - Leave a comment</a> Mon, 14 Apr 2008 23:01:00 -0000http://commandline.org.uk/python/2008/apr/15/sharing-our-scripts-together/