First batch of extra files
132
eps/hpr1286/hpr1286_full_shownotes.html
Executable file
@@ -0,0 +1,132 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta content="pandoc" name="generator" />
|
||||||
|
<meta content="width=device-width, initial-scale=1.0, user-scalable=yes" name="viewport" />
|
||||||
|
<meta content="Dave Morriss" name="author" />
|
||||||
|
<title>iCalendar Hacking (HPR Show 1286)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link href="http://hackerpublicradio.org/css/hpr.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body id="home">
|
||||||
|
<div class="shadow" id="container"><header><h1 class="title">iCalendar Hacking (HPR Show 1286)</h1>
|
||||||
|
<h2 class="subtitle">Creating iCalendar rules by hand and with a Perl script</h2>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr /></header> <main id="maincontent"> <article> <header><h1>Table of Contents</h1><nav id="TOC"><ul>
|
||||||
|
<li><a href="#editors-note-2020-01-02">Editor’s Note 2020-01-02</a></li>
|
||||||
|
<li><a href="#the-problem">The Problem</a></li>
|
||||||
|
<li><a href="#research">Research</a></li>
|
||||||
|
<li><a href="#experiment-1">Experiment 1</a></li>
|
||||||
|
<li><a href="#experiment-2">Experiment 2</a></li>
|
||||||
|
<li><a href="#experiment-3">Experiment 3</a></li>
|
||||||
|
<li><a href="#conclusion">Conclusion</a></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul></nav> </header><h2 id="editors-note-2020-01-02">Editor’s Note 2020-01-02</h2>
|
||||||
|
<p>The notes for this episode have been reformatted, particularly the long-form notes. This was done to make them more readable. Also, the original Git repository has been changed from <em>Gitorious</em> to <em>GitLab</em>.</p>
|
||||||
|
<p>In 2019 an iCalendar file was placed on the HPR server at <a href="http://hackerpublicradio.org/HPR_Community_News_schedule.ics">http://hackerpublicradio.org/HPR_Community_News_schedule.ics</a> which you can use in your own calendar application. The file contains the recording times of 12 months of Community News shows and is updated monthly.</p>
|
||||||
|
<h2 id="the-problem">The Problem</h2>
|
||||||
|
<p>Back in 2012 Ken Fallon tried to use Google Calendar to set up an event for the recording of the monthly <em>Community News</em> shows on HPR. He wanted to set these on the Saturday before the first Monday of the month. Surprisingly he didn’t find a way to do this and ended up deleting the attempt.</p>
|
||||||
|
<p>I looked at the calendaring application I use: <em>Thunderbird</em> with the <em>Lightning</em> calendar plugin, to see if I could manage it. I also couldn’t find a way.</p>
|
||||||
|
<p>This episode documents my journey to find a way to make the calendar entries we need.</p>
|
||||||
|
<h2 id="research">Research</h2>
|
||||||
|
<p>I was aware that calendars like Google Calendar and many more use iCalendar to represent and communicate events, and so I thought I would try and find out more. I have often wondered how iCalendar calendaring works, so I grabbed a copy of <strong>RFC 5545</strong> and absorbed enough to get a vague idea of how it defines recurrent entries. If you’d like to read this yourself have a look at <a class="uri" href="http://www.ietf.org/rfc/rfc5545.txt">http://www.ietf.org/rfc/rfc5545.txt</a></p>
|
||||||
|
<p>There are two primary methods of defining recurrent events within iCalendar: <strong>RRULE</strong> and <strong>RDATE</strong>. The RRULE property is the more powerful of the two and the more complex. The description of RRULE is long and involved, but in the context of this problem I could see how to define the first Monday of every month:</p><pre><code>RRULE:FREQ=MONTHLY;BYDAY=1MO</code></pre><ul>
|
||||||
|
<li><strong><code>FREQ=MONTHLY</code></strong> simply means the rule repeats every month.</li>
|
||||||
|
<li><strong><code>BYDAY=1MO</code></strong> then means that every month the first Monday is selected.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Most calendar applications are well able to deal with this sort of specification, and it seems to be the way in which most recurrent events are defined.</p>
|
||||||
|
<h2 id="experiment-1">Experiment 1</h2>
|
||||||
|
<p>However, this is not what we want. We need the Saturday before the first Monday, but the iCalendar syntax doesn’t have any obvious way of subtracting 2 days to get the Saturday before, especially when it could be in the previous month.</p>
|
||||||
|
<p>The definition of the <strong>BYDAY</strong> rule part specifies a comma separated list of days of the week (MO, TU, WE, TH, FR, SA, SU). As we have seen these weekday specifications can also be preceded by a digit as in 1MO.</p>
|
||||||
|
<p>There is also a rule part <strong>BYSETPOS</strong> which modifies the BYDAY rule part. It is followed by a comma separated list of values which corresponds to the nth occurrence within the set of events specified by the rule.</p>
|
||||||
|
<p>This led me to believe that I could make a rule as follows:</p><pre><code>RRULE:FREQ=MONTHLY;BYDAY=SA,SU,1MO;BYSETPOS=1</code></pre><ul>
|
||||||
|
<li><strong><code>FREQ=MONTHLY</code></strong> as before means the rule repeats every month.</li>
|
||||||
|
<li><strong><code>BYDAY=SA,SU,1MO</code></strong> then means that every month the weekend before the first Monday is selected.</li>
|
||||||
|
<li><strong><code>BYSETPOS=1</code></strong> means to select the first first day of the group, the Saturday</li>
|
||||||
|
</ul>
|
||||||
|
<p>I was rather surprised to find that this actually worked, but soon discovered that it has a fatal flaw. If the three days in BYDAY are all in the same month it works fine, but if either the Saturday or Sunday are in the previous month it can’t backtrack far enough and drops the event on the wrong day.</p>
|
||||||
|
<p>Even if this worked, I suspect many calendar applications couldn’t define it anyway. <em>Thunderbird+Lightning</em> cannot for certain. The user interface is just not able to specify this amount of detail.</p>
|
||||||
|
<p>The following is the full iCalendar entry that I plugged into <em>Thunderbird</em>:</p><pre><code>BEGIN:VCALENDAR
|
||||||
|
PRODID:MyCal
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20130629T190000Z
|
||||||
|
DTEND:20130629T210000Z
|
||||||
|
LOCATION:mumble.openspeak.cc port: 64747
|
||||||
|
RRULE:FREQ=MONTHLY;BYDAY=SA,SU,1MO;BYSETPOS=1
|
||||||
|
SUMMARY:HPR Community News
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR</code></pre><h2 id="experiment-2">Experiment 2</h2>
|
||||||
|
<p>However, I discovered there is an alternative way through the <strong>RDATE</strong> specification. With it you can define a number of events by pre-computing them. I was able to build a test calendar containing the next twelve <em>Community News</em> events (there’s naturally a plug-in for Vim which recognises the syntax!), load it into <em>Thunderbird</em> and make it send out invitations.</p>
|
||||||
|
<p>In true Hacker style I wrote a Perl <a href="make_meeting.html">script</a><a class="footnote-ref" href="#fn1" id="fnref1"><sup>1</sup></a> to generate the necessary <strong>RDATE</strong> dates. The script uses the Perl module <em>Date::Calc</em> to perform date calculations and <em>Data::ICal</em> to generate iCalendar data.</p>
|
||||||
|
<p>Running the script in the following way:</p><pre><code>./make_meeting > experiment2.ics</code></pre><p>generates a file containing 12 appointments that can be loaded into Thunderbird (and presumably any other iCalendar-based calendar).</p><pre><code>BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:Data::ICal 0.20
|
||||||
|
X-WR-CALNAME:Hacker Public Radio
|
||||||
|
X-WR-TIMEZONE:Europe/London
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DESCRIPTION:This is a test\, building an iCalendar file and loading it into
|
||||||
|
Thunderbird.\n-----------------------------------------\nMumble settings\
|
||||||
|
nServer Name: Anything you like\nServer Address: mumble.openspeak.cc \nPor
|
||||||
|
t: 64747\nName: Your name or alias is fine\n\nDon't have mumble\, setup in
|
||||||
|
structions can be found on our wiki -\nhttp://linuxbasix.com/tiki-index.ph
|
||||||
|
p?page=Linux+Basix+Mumble\n
|
||||||
|
DTEND:20130803T210000Z
|
||||||
|
DTSTART:20130803T190000Z
|
||||||
|
LOCATION:mumble.openspeak.cc port: 64747
|
||||||
|
RDATE;VALUE=DATE-TIME:20130803T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20130831T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20131005T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20131102T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20131130T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140104T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140201T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140301T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140405T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140503T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140531T190000Z
|
||||||
|
RDATE;VALUE=DATE-TIME:20140705T190000Z
|
||||||
|
SUMMARY:HPR Community News
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR</code></pre><p>Thunderbird’s event dialog will not let you edit the sub-events, just delete them, but the idea works, albeit in a rather clunky way.</p>
|
||||||
|
<p>I don’t have access to many other calendaring systems, except for Korganizer. It sees the multiple dates as multiple discrete events rather than a single recurring event.</p>
|
||||||
|
<h2 id="experiment-3">Experiment 3</h2>
|
||||||
|
<p>Other calendaring systems that do not use iCalendar can handle this problem more effectively. For many years I have used a tool called <em>pcal</em> (<a class="uri" href="http://pcal.sourceforge.net/">http://pcal.sourceforge.net/</a>) that generates PostScript calendars which I print and hang on the wall. It can reliably specify the Saturday before the first Monday of each month with the expression:</p><pre><code>Saturday before first Monday in all HPR Community News (19:00 - 21:00)</code></pre><p>Another tool which can do this is <em>Remind</em> (<a class="uri" href="http://www.roaringpenguin.com/products/remind">http://www.roaringpenguin.com/products/remind</a>, <a class="uri" href="http://www.linuxjournal.com/article/3529">http://www.linuxjournal.com/article/3529</a>). With this the following expression achieves the required result:</p><pre><code>REM Mon 1 --2 AT 19:00 MSG HPR Community News (19:00 - 21:00)</code></pre><p>Remind comes with a tool which can generate iCalendar data, called <em>rem2ics</em>. It expects output from the <em>remind</em> command from which it generates data. The following example generates 12 meetings from the above reminder which is in the file <em>.reminders</em>.</p><pre><code>remind -s12 .reminders | TZ=UTC rem2ics -do >reminders.ics</code></pre><p>The result uses the RDATE specification as discussed in Experiment 2, with the same result.</p><pre><code>BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:http://mark.atwood.name/code/rem2ics rem2ics 0.93
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:rem2ics.50a24176.3f5d.1@localhost
|
||||||
|
SUMMARY:HPR Community News (19\:00 - 21\:00)
|
||||||
|
DTSTART;TZID=UTC:20121103T190000
|
||||||
|
RDATE;TZID=UTC:20121103T190000,20121201T190000,20130105T190000,20130202T
|
||||||
|
190000,20130302T190000,20130330T190000,20130504T190000,20130601T190000,2
|
||||||
|
0130629T190000,20130803T190000,20130831T190000,20131005T190000
|
||||||
|
DTSTAMP:20121113Z124750Z
|
||||||
|
COMMENT: generated by rem2ics 0.93\n http://mark.atwood.name/code/rem2ic
|
||||||
|
s\n data[1]=|2012/11/03 * * * 1140 7\:00pm HPR Community News (19\:00 -
|
||||||
|
21\:00)|
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR</code></pre><h2 id="conclusion">Conclusion</h2>
|
||||||
|
<p>It seems that the iCalendar specification <strong>should</strong> be able to generate the appointments we need using the compact RRULE specification. However, in the (admittedly small) sample of calendaring applications checked this does not seem to have been implemented properly.</p>
|
||||||
|
<p>Other tools that do not use iCalendar have less difficulty representing such events but are not as widely adopted.</p>
|
||||||
|
<p>If anyone has any ideas about how this problem could be solved more effectively then please let me know!</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Detailed show notes: <a href="full_shownotes.html">full_shownotes.html</a></li>
|
||||||
|
<li>My <em>make_meeting</em> script for download - <a class="uri" href="https://gitlab.com/davmo/hprmisc/blob/master/make_meeting">https://gitlab.com/davmo/hprmisc/blob/master/make_meeting</a></li>
|
||||||
|
<li>Wikipedia iCalendar entry - <a class="uri" href="http://en.wikipedia.org/wiki/ICalendar">http://en.wikipedia.org/wiki/ICalendar</a></li>
|
||||||
|
<li>iCalendar RFC - <a class="uri" href="http://www.ietf.org/rfc/rfc5545.txt">http://www.ietf.org/rfc/rfc5545.txt</a></li>
|
||||||
|
<li>pcal PostScript calendar generator - <a class="uri" href="http://pcal.sourceforge.net/">http://pcal.sourceforge.net/</a></li>
|
||||||
|
<li>Remind - <a class="uri" href="http://www.roaringpenguin.com/products/remind">http://www.roaringpenguin.com/products/remind</a>, <a class="uri" href="http://www.linuxjournal.com/article/3529">http://www.linuxjournal.com/article/3529</a></li>
|
||||||
|
</ul><section class="footnotes"><hr />
|
||||||
|
<ol>
|
||||||
|
<li id="fn1">
|
||||||
|
<p>Note, this is a viewable HTML version of the script, but a downloadable version exists in a Git repository. See the <b>Links</b> section for details.<a class="footnote-back" href="#fnref1">↩</a></p>
|
||||||
|
</li>
|
||||||
|
</ol></section> </article> </main></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
249
eps/hpr1286/make_meeting.html
Executable file
@@ -0,0 +1,249 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<!-- Generated by perltidy on Mon Jul 1 13:37:55 2013 -->
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>make_meeting</title>
|
||||||
|
<style type="text/css">
|
||||||
|
<!--
|
||||||
|
/* default style sheet generated by perltidy */
|
||||||
|
body {background: #FFFFFF; color: #000000}
|
||||||
|
pre { color: #000000;
|
||||||
|
background: #FFFFFF;
|
||||||
|
font-family: courier;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c { color: #228B22;} /* comment */
|
||||||
|
.cm { color: #000000;} /* comma */
|
||||||
|
.co { color: #000000;} /* colon */
|
||||||
|
.h { color: #CD5555; font-weight:bold;} /* here-doc-target */
|
||||||
|
.hh { color: #CD5555; font-style:italic;} /* here-doc-text */
|
||||||
|
.i { color: #00688B;} /* identifier */
|
||||||
|
.j { color: #CD5555; font-weight:bold;} /* label */
|
||||||
|
.k { color: #8B008B; font-weight:bold;} /* keyword */
|
||||||
|
.m { color: #FF0000; font-weight:bold;} /* subroutine */
|
||||||
|
.n { color: #B452CD;} /* numeric */
|
||||||
|
.p { color: #000000;} /* paren */
|
||||||
|
.pd { color: #228B22; font-style:italic;} /* pod-text */
|
||||||
|
.pu { color: #000000;} /* punctuation */
|
||||||
|
.q { color: #CD5555;} /* quote */
|
||||||
|
.s { color: #000000;} /* structure */
|
||||||
|
.sc { color: #000000;} /* semicolon */
|
||||||
|
.v { color: #B452CD;} /* v-string */
|
||||||
|
.w { color: #000000;} /* bareword */
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a name="-top-"></a>
|
||||||
|
<h1>make_meeting</h1>
|
||||||
|
<!-- BEGIN CODE INDEX --><a name="code-index"></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#make_date-">package main</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#make_date-">make_date</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a href="#EOF-">EOF</a></li>
|
||||||
|
</ul>
|
||||||
|
<!-- END CODE INDEX -->
|
||||||
|
<hr />
|
||||||
|
<!-- contents of filename: make_meeting -->
|
||||||
|
<pre>
|
||||||
|
#!/usr/bin/perl
|
||||||
|
<span class="c">#===============================================================================</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># FILE: make_meeting</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># USAGE: ./make_meeting</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># DESCRIPTION: Makes a recurrent iCalendar meeting to be loaded into</span>
|
||||||
|
<span class="c"># a calendar. This is apparently necessary when the 'RRULE'</span>
|
||||||
|
<span class="c"># recurrence description is not adequate.</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># OPTIONS: None</span>
|
||||||
|
<span class="c"># REQUIREMENTS: Needs modules Data::ICal and Date::Calc</span>
|
||||||
|
<span class="c"># BUGS: ---</span>
|
||||||
|
<span class="c"># NOTES: Distributed with the HPR episode "iCalendar Hacking"</span>
|
||||||
|
<span class="c"># AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com</span>
|
||||||
|
<span class="c"># LICENCE: Copyright (c) year 2012, Dave Morriss</span>
|
||||||
|
<span class="c"># VERSION: 1.0</span>
|
||||||
|
<span class="c"># CREATED: 13/10/2012 15:34:01</span>
|
||||||
|
<span class="c"># REVISION: 16/11/2012 16:04:37</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c">#===============================================================================</span>
|
||||||
|
<span class="c"># This program is free software: you can redistribute it and/or modify it</span>
|
||||||
|
<span class="c"># under the terms of the GNU General Public License as published by the Free</span>
|
||||||
|
<span class="c"># Software Foundation, either version 3 of the License, or (at your option)</span>
|
||||||
|
<span class="c"># any later version.</span>
|
||||||
|
<span class="c"># </span>
|
||||||
|
<span class="c"># This program is distributed in the hope that it will be useful, but WITHOUT</span>
|
||||||
|
<span class="c"># ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or</span>
|
||||||
|
<span class="c"># FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for</span>
|
||||||
|
<span class="c"># more details.</span>
|
||||||
|
<span class="c"># </span>
|
||||||
|
<span class="c"># You should have received a copy of the GNU General Public License along with</span>
|
||||||
|
<span class="c"># this program. If not, see <http://www.gnu.org/licenses/>.</span>
|
||||||
|
<span class="c">#===============================================================================</span>
|
||||||
|
|
||||||
|
<span class="k">use</span> <span class="n">5.010</span><span class="sc">;</span>
|
||||||
|
<span class="k">use</span> <span class="w">strict</span><span class="sc">;</span>
|
||||||
|
<span class="k">use</span> <span class="w">warnings</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="k">use</span> <span class="w">Data::ICal</span><span class="sc">;</span>
|
||||||
|
<span class="k">use</span> <span class="w">Data::ICal::Entry::Event</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="k">use</span> <span class="w">Date::Calc</span> <span class="q">qw{</span>
|
||||||
|
<span class="q">Today Day_of_Year Add_Delta_YMD Nth_Weekday_of_Month_Year</span>
|
||||||
|
<span class="q">}</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Date and time values</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">my</span> <span class="i">@today</span> = <span class="i">Today</span><span class="s">(</span><span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">@startdate</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">@rdate</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">$monday</span> = <span class="n">1</span><span class="sc">;</span> <span class="c"># Day of week number 1-7, Monday-Sunday</span>
|
||||||
|
|
||||||
|
<span class="k">my</span> <span class="i">@starttime</span> = <span class="s">(</span> <span class="n">19</span><span class="cm">,</span> <span class="n">00</span><span class="cm">,</span> <span class="n">00</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">@endtime</span> = <span class="s">(</span> <span class="n">21</span><span class="cm">,</span> <span class="n">00</span><span class="cm">,</span> <span class="n">00</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Format of an ISO UTC datetime</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">my</span> <span class="i">$fmt</span> = <span class="q">"%02d%02d%02dT%02d%02d%02dZ"</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Constants for the event</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">my</span> <span class="i">$calname</span> = <span class="q">'Hacker Public Radio'</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">$timezone</span> = <span class="q">'Europe/London'</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">$location</span> = <span class="q">'mumble.openspeak.cc port: 64747'</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">$summary</span> = <span class="q">'HPR Community News'</span><span class="sc">;</span>
|
||||||
|
<span class="k">my</span> <span class="i">$description</span> = <span class="h"><<ENDDESC</span><span class="sc">;</span>
|
||||||
|
<span class="hh">This is a test, building an iCalendar file and loading it into Thunderbird.</span>
|
||||||
|
<span class="hh">-----------------------------------------</span>
|
||||||
|
<span class="hh">Mumble settings</span>
|
||||||
|
<span class="hh">Server Name: Anything you like</span>
|
||||||
|
<span class="hh">Server Address: mumble.openspeak.cc </span>
|
||||||
|
<span class="hh">Port: 64747</span>
|
||||||
|
<span class="hh">Name: Your name or alias is fine</span>
|
||||||
|
|
||||||
|
<span class="hh">Don't have mumble, setup instructions can be found on our wiki -</span>
|
||||||
|
<span class="hh">http://linuxbasix.com/tiki-index.php?page=Linux+Basix+Mumble</span>
|
||||||
|
<span class="h">ENDDESC</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Compute the next meeting date from now</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="i">@startdate</span> = <span class="i">make_date</span><span class="s">(</span> \<span class="i">@today</span><span class="cm">,</span> <span class="i">$monday</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">-2</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Create the calendar object</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">my</span> <span class="i">$calendar</span> = <span class="w">Data::ICal</span><span class="w">->new</span><span class="s">(</span><span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Some calendar properties</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="i">$calendar</span><span class="i">->add_properties</span><span class="s">(</span>
|
||||||
|
<span class="q">'X-WR-CALNAME'</span> <span class="cm">=></span> <span class="i">$calname</span><span class="cm">,</span>
|
||||||
|
<span class="q">'X-WR-TIMEZONE'</span> <span class="cm">=></span> <span class="i">$timezone</span><span class="cm">,</span>
|
||||||
|
<span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Create the event object</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">my</span> <span class="i">$vevent</span> = <span class="w">Data::ICal::Entry::Event</span><span class="w">->new</span><span class="s">(</span><span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Add some event properties</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="i">$vevent</span><span class="i">->add_properties</span><span class="s">(</span>
|
||||||
|
<span class="w">summary</span> <span class="cm">=></span> <span class="i">$summary</span><span class="cm">,</span>
|
||||||
|
<span class="w">location</span> <span class="cm">=></span> <span class="i">$location</span><span class="cm">,</span>
|
||||||
|
<span class="w">description</span> <span class="cm">=></span> <span class="i">$description</span><span class="cm">,</span>
|
||||||
|
<span class="w">dtstart</span> <span class="cm">=></span> <span class="k">sprintf</span><span class="s">(</span> <span class="i">$fmt</span><span class="cm">,</span> <span class="i">@startdate</span><span class="cm">,</span> <span class="i">@starttime</span> <span class="s">)</span><span class="cm">,</span>
|
||||||
|
<span class="w">dtend</span> <span class="cm">=></span> <span class="k">sprintf</span><span class="s">(</span> <span class="i">$fmt</span><span class="cm">,</span> <span class="i">@startdate</span><span class="cm">,</span> <span class="i">@endtime</span> <span class="s">)</span><span class="cm">,</span>
|
||||||
|
<span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Add 12 recurring dates. (Note that this generates 12 RDATE entries rather</span>
|
||||||
|
<span class="c"># than 1 entry with multiple dates; this is because this module doesn't seem</span>
|
||||||
|
<span class="c"># to have the ability to generated the concatenated entry. The two modes of</span>
|
||||||
|
<span class="c"># expressing the repeated dates seem to be equivalent.)</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">for</span> <span class="k">my</span> <span class="i">$i</span> <span class="s">(</span> <span class="n">1</span> .. <span class="n">12</span> <span class="s">)</span> <span class="s">{</span>
|
||||||
|
<span class="i">@today</span> = <span class="i">Add_Delta_YMD</span><span class="s">(</span> <span class="i">@today</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">0</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="i">@rdate</span> = <span class="i">make_date</span><span class="s">(</span> \<span class="i">@today</span><span class="cm">,</span> <span class="i">$monday</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">-2</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="i">$vevent</span><span class="i">->add_property</span><span class="s">(</span> <span class="w">rdate</span> <span class="cm">=></span>
|
||||||
|
<span class="s">[</span> <span class="k">sprintf</span><span class="s">(</span> <span class="i">$fmt</span><span class="cm">,</span> <span class="i">@rdate</span><span class="cm">,</span> <span class="i">@starttime</span> <span class="s">)</span><span class="cm">,</span> <span class="s">{</span> <span class="w">value</span> <span class="cm">=></span> <span class="q">'DATE-TIME'</span> <span class="s">}</span> <span class="s">]</span><span class="cm">,</span>
|
||||||
|
<span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="s">}</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Add the event into the calendar</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="i">$calendar</span><span class="i">->add_entry</span><span class="s">(</span><span class="i">$vevent</span><span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Print the result</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">print</span> <span class="i">$calendar</span><span class="i">->as_string</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="k">exit</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#=== FUNCTION ================================================================</span>
|
||||||
|
<span class="c"># NAME: make_date</span>
|
||||||
|
<span class="c"># PURPOSE: Make the event date for recurrence</span>
|
||||||
|
<span class="c"># PARAMETERS: $refdate</span>
|
||||||
|
<span class="c"># An arrayref to the reference date array (usually</span>
|
||||||
|
<span class="c"># today's date)</span>
|
||||||
|
<span class="c"># $dow Day of week for the event date (1-7, 1=Monday)</span>
|
||||||
|
<span class="c"># $n The nth day of the week in the given month required</span>
|
||||||
|
<span class="c"># for the event date</span>
|
||||||
|
<span class="c"># $offset Number of days to offset the computed date</span>
|
||||||
|
<span class="c"># RETURNS: The resulting date as a list for Date::Calc</span>
|
||||||
|
<span class="c"># DESCRIPTION: We want to compute a simple date with an offset, such as</span>
|
||||||
|
<span class="c"># "the Saturday before the first Monday of the month". We do</span>
|
||||||
|
<span class="c"># this my computing a pre-offset date (first Monday of month)</span>
|
||||||
|
<span class="c"># then apply the offset (Saturday before).</span>
|
||||||
|
<span class="c"># THROWS: No exceptions</span>
|
||||||
|
<span class="c"># COMMENTS: TODO Needs more testing to be considered truly universal</span>
|
||||||
|
<span class="c"># SEE ALSO:</span>
|
||||||
|
<span class="c">#===============================================================================</span>
|
||||||
|
<a name="make_date-"></a><span class="k">sub </span><span class="m">make_date</span> <span class="s">{</span>
|
||||||
|
<span class="k">my</span> <span class="s">(</span> <span class="i">$refdate</span><span class="cm">,</span> <span class="i">$dow</span><span class="cm">,</span> <span class="i">$n</span><span class="cm">,</span> <span class="i">$offset</span> <span class="s">)</span> = <span class="i">@_</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Compute the required date: the nth day of week in this year and month</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">my</span> <span class="i">@date</span> = <span class="i">Nth_Weekday_of_Month_Year</span><span class="s">(</span> <span class="i">@$refdate</span>[ <span class="n">0</span><span class="cm">,</span> <span class="n">1</span> ]<span class="cm">,</span> <span class="i">$dow</span><span class="cm">,</span> <span class="i">$n</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># If the computed date is before the base date advance a month</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">if</span> <span class="s">(</span> <span class="i">Day_of_Year</span><span class="s">(</span><span class="i">@date</span><span class="s">)</span> <= <span class="i">Day_of_Year</span><span class="s">(</span><span class="i">@$refdate</span><span class="s">)</span> <span class="s">)</span> <span class="s">{</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Add a month and recompute</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="i">@date</span> = <span class="i">Add_Delta_YMD</span><span class="s">(</span> <span class="i">@date</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="n">1</span><span class="cm">,</span> <span class="n">0</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="i">@date</span> = <span class="i">Nth_Weekday_of_Month_Year</span><span class="s">(</span> <span class="i">@date</span>[ <span class="n">0</span><span class="cm">,</span> <span class="n">1</span> ]<span class="cm">,</span> <span class="i">$dow</span><span class="cm">,</span> <span class="i">$n</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="s">}</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Apply the day offset</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="i">@date</span> = <span class="i">Add_Delta_YMD</span><span class="s">(</span> <span class="i">@date</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="n">0</span><span class="cm">,</span> <span class="i">$offset</span> <span class="s">)</span><span class="sc">;</span>
|
||||||
|
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="c"># Return a list</span>
|
||||||
|
<span class="c">#</span>
|
||||||
|
<span class="k">return</span> <span class="s">(</span><span class="i">@date</span><span class="s">)</span><span class="sc">;</span>
|
||||||
|
<span class="s">}</span>
|
||||||
|
|
||||||
|
<span class="c"># vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker</span>
|
||||||
|
|
||||||
|
<a name="EOF-"></a></pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1648/hpr1648_diagram.pdf
Executable file
240
eps/hpr1648/hpr1648_full_shownotes.html
Executable file
@@ -0,0 +1,240 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="hpr1648---full-show-notes">HPR1648 - full show notes</h1>
|
||||||
|
<ul>
|
||||||
|
<li>Title: Bash parameter manipulation</li>
|
||||||
|
<li>Host: Dave Morriss</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="bash-parameter-manipulation">Bash parameter manipulation</h2>
|
||||||
|
<p>I'm a great fan of using the Linux command line and enjoy writing shell scripts using the Bash shell.</p>
|
||||||
|
<ul>
|
||||||
|
<li><p><em>BASH</em> (or more usually <em>Bash</em> or <em>bash</em>) is the name of a <a href="http://en.wikipedia.org/wiki/Unix_shell">Unix shell</a>. The name stands for <em>Bourne Again SHell</em>, which is a play on words. Bash is an extension of the shell originally written by <a href="http://en.wikipedia.org/wiki/Stephen_Richard_Bourne">Stephen Bourne</a> in 1978, usually known as <em>SH</em>.</p></li>
|
||||||
|
<li><p><a href="http://en.wikipedia.org/wiki/Bash_%28Unix_shell%29">Bash</a> was written as part of the <a href="http://en.wikipedia.org/wiki/GNU_Project">GNU Project</a> which forms part of the Linux Operating System.</p></li>
|
||||||
|
<li><p>A shell is the part of the operating system that interprets commands, more commonly known as the command line.</p></li>
|
||||||
|
<li><p>A knowledge of Bash is very helpful if you would like to be able to use the power of the command line. It is also the way to learn how to build Bash scripts for automating the tasks you need to perform.</p></li>
|
||||||
|
</ul>
|
||||||
|
<p>In this episode we look at what parameters are in Bash, and how they can be created and manipulated. There are many features in Bash that you can use to do this, but they are not easy to find.</p>
|
||||||
|
<p>As I was learning my way around Bash it took me a while to find these. Once I had found them I wanted to make a "cheat sheet" I could stick on the wall to remind me how to do things. I am sharing the result of this process with you.</p>
|
||||||
|
<p>The version of Bash which I used for this episode is <em>4.3.30(1)-release</em></p>
|
||||||
|
<h3 id="what-is-a-parameter">What is a parameter?</h3>
|
||||||
|
<p>A Bash parameter, more commonly referred to as a variable, is a named item which holds a value. Parameters are created thus:</p>
|
||||||
|
<pre><code>username='droog'</code></pre>
|
||||||
|
<p>There should be no spaces before or after the '=' sign. The parameter called <code>username</code> now contains the string 'droog'.</p>
|
||||||
|
<p>To use the contents of <code>username</code> the name should be prefixed with a dollar ($) sign as in:</p>
|
||||||
|
<pre><code>echo "Your username is $username"
|
||||||
|
-> Your username is droog</code></pre>
|
||||||
|
<p>The line beginning '<strong>-></strong>' is what will be generated by the above statement. I will be using this method of signifying output through these notes.</p>
|
||||||
|
<p>An alternative way of referring to the contents of a parameter is to enclose it with curly brackets (braces) after the dollar sign. This makes the variable name unambiguous when there is a possibility of misinterpretation</p>
|
||||||
|
<h3 id="arrays">Arrays</h3>
|
||||||
|
<p>As well as the simple parameters seen so far, Bash also provides <em>arrays</em>. The simplest form is indexed by integer numbers, starting with zero and is defined either as a bracketed list:</p>
|
||||||
|
<pre><code>weekdays=(monday tuesday wednesday thursday friday)</code></pre>
|
||||||
|
<p>or individually as indexed elements:</p>
|
||||||
|
<pre><code>weekend[0]='saturday'
|
||||||
|
weekend[1]='sunday'</code></pre>
|
||||||
|
<p>There is a lot more to arrays in Bash than this, as well as there being <em>associative arrays</em> indexed by strings, but we will leave them for another time.</p>
|
||||||
|
<p>Referring to arrays is achieved using curly braces and indices:</p>
|
||||||
|
<pre><code>echo ${weekdays[4]}
|
||||||
|
-> friday</code></pre>
|
||||||
|
<p>The entire array can be referenced with '@' or '*' as an index:</p>
|
||||||
|
<pre><code>echo ${weekdays[@]}
|
||||||
|
-> monday tuesday wednesday thursday friday</code></pre>
|
||||||
|
<p>Knowing this much about arrays is necessary to understand the following parameter manipulation expressions.</p>
|
||||||
|
<h3 id="manipulating-parameters">Manipulating parameters</h3>
|
||||||
|
<p>If you want to change the value of a parameter there are many ways of doing it. A lot of scripts you may encounter might use <em>sed</em>, <em>awk</em> or <em>cut</em> to do this. For example, you might see this:</p>
|
||||||
|
<pre><code>date='2014-10-27'
|
||||||
|
month=$(echo $date | cut -f2 -d'-')
|
||||||
|
echo "The month number is $month"
|
||||||
|
-> The month number is 10</code></pre>
|
||||||
|
<p>Here <em>cut</em> was used to do the job of extracting the '10' from the contents of <code>date</code>. However, this is inefficient since it causes a whole new process to be run just to do this simple thing. Bash contains the facilities to do this all by itself. Here's how, using <em>substring expansion</em>:</p>
|
||||||
|
<pre><code>month=${date:5:2}</code></pre>
|
||||||
|
<p>Variable <code>month</code> is set to the characters of <code>date</code> from position 5 for 2 characters (the position is zero based by the way).</p>
|
||||||
|
<h3 id="parameter-manipulation-features">Parameter manipulation features</h3>
|
||||||
|
<p>I have demonstrated each of these briefly. Included with these notes are two other resources: the relevant text from the bash man page and some examples in diagrammatic form. Both are PDF files generated from LibreOffice.</p>
|
||||||
|
<p>The man page extract was originally made for my own benefit as a cheat sheet to help to remind me how to use these features. If it benefits you then great. If not then no problem.</p>
|
||||||
|
<p>The diagram was also meant for me to place on a pin-board over my desk, so it includes colour and seems a little more friendly. If you like it then you're welcome.</p>
|
||||||
|
<p>I have also included the examples below, with a little more explanation. I hope it helps.</p>
|
||||||
|
<h4 id="use-default-values">Use default values</h4>
|
||||||
|
<p>Returns the value of the parameter unless it's not defined or is null, in which case the default is returned:</p>
|
||||||
|
<pre><code>unset name
|
||||||
|
echo ${name:-Undefined}
|
||||||
|
-> Undefined
|
||||||
|
name="Charlie"
|
||||||
|
echo ${name:-Undefined}
|
||||||
|
-> Charlie</code></pre>
|
||||||
|
<h4 id="assign-default-values">Assign default values</h4>
|
||||||
|
<p>Set the value of the parameter if it is undefined or null:</p>
|
||||||
|
<pre><code>unset page
|
||||||
|
echo ${page:=portrait}
|
||||||
|
-> portrait
|
||||||
|
echo $page
|
||||||
|
-> portrait
|
||||||
|
page="landscape"
|
||||||
|
echo ${page:=portrait}
|
||||||
|
-> landscape
|
||||||
|
echo $page
|
||||||
|
-> landscape</code></pre>
|
||||||
|
<h4 id="display-error-if-null-or-unset">Display Error if Null or Unset</h4>
|
||||||
|
<p>Displays an error and causes the enclosing script to exit if the parameter is not set (the error message contains details of where in the script the problem occurred):</p>
|
||||||
|
<pre><code>echo ${length:?length is unset}
|
||||||
|
-> ... length: length is unset</code></pre>
|
||||||
|
<h4 id="use-alternate-value">Use Alternate Value</h4>
|
||||||
|
<p>If the parameter is null or unset then nothing is substituted.</p>
|
||||||
|
<pre><code>fish="trout"
|
||||||
|
echo ${fish:+salmon}
|
||||||
|
-> salmon
|
||||||
|
echo $fish
|
||||||
|
-> trout</code></pre>
|
||||||
|
<h4 id="substring-expansion">Substring Expansion</h4>
|
||||||
|
<p>This one is quite complex. The first value is an offset and the second a length. If the length is omitted then the rest of the string is returned.</p>
|
||||||
|
<p>A negative offset means to count backwards from the end of the string. Note that the sign must be preceded by a space to avoid being misinterpreted as the <em>default value</em> form.</p>
|
||||||
|
<p>A negative length is not really a length, but means to return the string between the offset and the position backwards from the end of the string.</p>
|
||||||
|
<p>Sections of arrays may also be indexed with this expression. The offset is an offset into the elements of the array and the length is a count of elements.</p>
|
||||||
|
<pre><code>animal="aardvark"
|
||||||
|
echo ${animal:4}
|
||||||
|
-> vark
|
||||||
|
message="No such file"
|
||||||
|
echo ${message:0:7}
|
||||||
|
-> No such
|
||||||
|
echo ${message: -4}
|
||||||
|
-> file
|
||||||
|
echo ${message:3:-4}
|
||||||
|
-> such
|
||||||
|
|
||||||
|
colours=(red orange yellow green blue indigo violet)
|
||||||
|
echo ${colours[@]:1:3}
|
||||||
|
-> orange yellow green
|
||||||
|
echo ${colours[@]:5}
|
||||||
|
-> indigo violet</code></pre>
|
||||||
|
<h4 id="names-matching-prefix">Names matching prefix</h4>
|
||||||
|
<p>This is for reporting names of variables. We do not show all the names in the examples below.</p>
|
||||||
|
<pre><code>echo ${!BASH*}
|
||||||
|
-> BASH BASHOPTS BASHPID ...
|
||||||
|
export coord_x=42
|
||||||
|
export coord_y=100
|
||||||
|
echo ${!coord*}
|
||||||
|
-> coord_x coord_y</code></pre>
|
||||||
|
<h4 id="list-of-array-keys">List of array keys</h4>
|
||||||
|
<p>Lists the array indices (more generally <em>keys</em>) in an array.</p>
|
||||||
|
<pre><code>colours=( red green blue )
|
||||||
|
echo ${!colours[@]}
|
||||||
|
-> 0 1 2</code></pre>
|
||||||
|
<h4 id="parameter-length">Parameter length</h4>
|
||||||
|
<p>Shows the length of a parameter. If used with an array it returns the number of elements.</p>
|
||||||
|
<p>Note the second example below saves the result of the <em>date</em> command in an array <code>dt</code>. There are 6 fields separated by spaces, so the array element count reflects this.</p>
|
||||||
|
<pre><code>veg="Broccoli"
|
||||||
|
echo ${#veg}
|
||||||
|
-> 8
|
||||||
|
dt=($(date))
|
||||||
|
echo ${#dt[@]}
|
||||||
|
-> 6</code></pre>
|
||||||
|
<h4 id="remove-matching-prefix-pattern">Remove matching prefix pattern</h4>
|
||||||
|
<p>This removes characters from the front of a string. The pattern used can contain an asterisk ('*') meaning an arbitrary number of characters. If two hash characters ('#') are used the longest match is removed.</p>
|
||||||
|
<p>So, in the first examples '*/' with one hash just removes the leading '/', but with two hashes everything up to and including the last '/' is removed. This is equivalent to the built-in <code>basename</code> command.</p>
|
||||||
|
<p>When applied to an array every element can be trimmed as shown in the second example. Note that here we are saving the result of trimming the leading '_' back into the array.</p>
|
||||||
|
<pre><code>dir="/home/dave/some/dir"
|
||||||
|
echo ${dir#/home/dave/}
|
||||||
|
-> some/dir
|
||||||
|
echo ${dir#*/}
|
||||||
|
-> home/dave/some/dir
|
||||||
|
echo ${dir##*/}
|
||||||
|
-> dir
|
||||||
|
|
||||||
|
colours=(_red _green _blue)
|
||||||
|
colours=(${colours[@]#_})
|
||||||
|
echo ${colours[@]}
|
||||||
|
-> red green blue</code></pre>
|
||||||
|
<h4 id="remove-matching-suffix-pattern">Remove matching suffix pattern</h4>
|
||||||
|
<p>This feature is similar to the previous one and removes characters from the end of a string. The pattern used to determine what to remove is the same as before, and the use of double '%' characters makes the deletion affect the maximum number of characters.</p>
|
||||||
|
<p>Note that using '/*' in the examples has an effect similar to the <code>dirname</code> command.</p>
|
||||||
|
<p>A common use is shown in the second example where the extension of a filename is deleted and replaced.</p>
|
||||||
|
<pre><code>dir="/home/dave/some/dir"
|
||||||
|
echo ${dir%/some/dir}
|
||||||
|
-> /home/dave
|
||||||
|
echo ${dir%/*}
|
||||||
|
-> /home/dave/some
|
||||||
|
echo ${dir%%/some/*}
|
||||||
|
-> /home/dave
|
||||||
|
|
||||||
|
filename='great_view.jpg'
|
||||||
|
filename="${filename%.jpg}.png"
|
||||||
|
echo $filename
|
||||||
|
-> great_view.png</code></pre>
|
||||||
|
<h4 id="pattern-substitution">Pattern substitution</h4>
|
||||||
|
<p>This feature permits quite sophisticated changes to be made to a string or an array. The first part after the first '/' is the pattern to match, and can contain '*' as described before. See the <a href="http://wiki.bash-hackers.org/syntax/pe">Bash Hackers</a> site for a very good explanation of this. The second string is what is to replace the target.</p>
|
||||||
|
<p>If two '/' characters follow the parameter name then all matches that are found are replaced.</p>
|
||||||
|
<p>If the pattern string begins with a '#' then it must match at the start of the parameter, however if the pattern string begins with a '%' then it must match at the end of the parameter.</p>
|
||||||
|
<pre><code>msg='An ant is an ant'
|
||||||
|
echo ${msg/ant/insect}
|
||||||
|
-> An insect is an ant
|
||||||
|
echo ${msg/%ant/insect}
|
||||||
|
-> An ant is an insect
|
||||||
|
echo ${msg//ant/insect}
|
||||||
|
-> An insect is an insect
|
||||||
|
|
||||||
|
colours=(red green blue)
|
||||||
|
echo ${colours[@]/green/yellow}
|
||||||
|
-> red yellow blue
|
||||||
|
echo ${colours[@]/#/_}
|
||||||
|
-> _red _green _blue</code></pre>
|
||||||
|
<h4 id="case-modification">Case modification</h4>
|
||||||
|
<p>Finally, this feature changes the case of letters. The '^' symbol makes matching letters into uppercase, and ',' converts to lowercase. A single '^' or ',' after the parameter name makes one change, whereas doubling this symbol changes every matching character.</p>
|
||||||
|
<p>Note the use of a pattern enclosed in square brackets matches the enclosed letters. Adding a '^' to the start of this list inverts the matching effect as seen below.</p>
|
||||||
|
<pre><code>msg='the quick brown fox'
|
||||||
|
echo ${msg^}
|
||||||
|
-> The quick brown fox
|
||||||
|
echo ${msg^^}
|
||||||
|
-> THE QUICK BROWN FOX
|
||||||
|
echo ${msg^^o}
|
||||||
|
-> the quick brOwn fOx
|
||||||
|
echo ${msg^^[tqbf]}
|
||||||
|
-> The Quick Brown Fox
|
||||||
|
echo ${msg^^[^tqbf]}
|
||||||
|
-> tHE qUICK bROWN fOX</code></pre>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Definitions:
|
||||||
|
<ul>
|
||||||
|
<li>Unix shell <a href="http://en.wikipedia.org/wiki/Unix_shell">http://en.wikipedia.org/wiki/Unix_shell</a></li>
|
||||||
|
<li>Bash <a href="http://en.wikipedia.org/wiki/Bash_%28Unix_shell%29">http://en.wikipedia.org/wiki/Bash_%28Unix_shell%29</a></li>
|
||||||
|
<li>GNU Project <a href="http://en.wikipedia.org/wiki/GNU_Project">http://en.wikipedia.org/wiki/GNU_Project</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>References
|
||||||
|
<ul>
|
||||||
|
<li>Shell Parameter Expansion <a href="http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html">http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html</a></li>
|
||||||
|
<li>Bash Hackers on parameter expansion <a href="http://wiki.bash-hackers.org/syntax/pe">http://wiki.bash-hackers.org/syntax/pe</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Previous HPR shows on the shell or shell scripting:
|
||||||
|
<ul>
|
||||||
|
<li>2008-03-03 HPR0045: Shell Scripting (dosman) <a href="http://hackerpublicradio.org/eps/hpr0045">http://hackerpublicradio.org/eps/hpr0045</a></li>
|
||||||
|
<li>2008-03-12 HPR0052: UCLUG: Newbie Shell Scripting (Dave Yates) <a href="http://hackerpublicradio.org/eps/hpr0052">http://hackerpublicradio.org/eps/hpr0052</a></li>
|
||||||
|
<li>2010-03-24 HPR0531: bash loops (Ken Fallon) <a href="http://hackerpublicradio.org/eps/hpr0531">http://hackerpublicradio.org/eps/hpr0531</a></li>
|
||||||
|
<li>2010-08-11 HPR0562: Introduction to bash scripting (Ken Fallon) <a href="http://hackerpublicradio.org/eps/hpr0562">http://hackerpublicradio.org/eps/hpr0562</a></li>
|
||||||
|
<li>2010-11-17 HPR0598: Bash Scripting: Episode 2 Command Line Basics (Ken Fallon) <a href="http://hackerpublicradio.org/eps/hpr0598">http://hackerpublicradio.org/eps/hpr0598</a></li>
|
||||||
|
<li>2012-05-22 HPR0992: Linux In The Shell 007 - Chmod and Unix Permissions. (Dann) <a href="http://hackerpublicradio.org/eps/hpr0992">http://hackerpublicradio.org/eps/hpr0992</a></li>
|
||||||
|
<li>2012-06-05 HPR1002: Linux In The Shell 008 - free: Understanding Linux Memory Usage (Dann) <a href="http://hackerpublicradio.org/eps/hpr1002">http://hackerpublicradio.org/eps/hpr1002</a></li>
|
||||||
|
<li>2013-03-05 HPR1197: What I do with bash scripts (Jon Kulp) <a href="http://hackerpublicradio.org/eps/hpr1197">http://hackerpublicradio.org/eps/hpr1197</a></li>
|
||||||
|
<li>2013-04-09 HPR1222: LiTS 027: mathematical commands (Dann) <a href="http://hackerpublicradio.org/eps/hpr1222">http://hackerpublicradio.org/eps/hpr1222</a></li>
|
||||||
|
<li>2013-05-14 HPR1247: Recording Terrestrial Radio with bash scipts and cron jobs (Jon Kulp) <a href="http://hackerpublicradio.org/eps/hpr1247">http://hackerpublicradio.org/eps/hpr1247</a></li>
|
||||||
|
<li>2013-05-22 HPR1253: Linux in the Shell Ep 30 - vmstat (Dann) <a href="http://hackerpublicradio.org/eps/hpr1253">http://hackerpublicradio.org/eps/hpr1253</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Resources
|
||||||
|
<ul>
|
||||||
|
<li>Full show notes <a href="hpr1648_full_shownotes.html">hpr1648_full_shownotes.html</a></li>
|
||||||
|
<li>Bash man page extract <a href="hpr1648_summary.pdf">hpr1648_summary.pdf</a></li>
|
||||||
|
<li>Parameter manipulation "<em>cheat sheet</em>" <a href="hpr1648_diagram.pdf">hpr1648_diagram.pdf</a></li>
|
||||||
|
<li>Various annotated Bash scripts which make up my Magnatune Downloader (described in show <a href="http://hackerpublicradio.org/eps/hpr1204">hpr1204</a>) <a href="https://gitorious.org/magnatune-downloader/magnatune-downloader/source/master:">https://gitorious.org/magnatune-downloader/magnatune-downloader/source/master:</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1648/hpr1648_summary.pdf
Executable file
BIN
eps/hpr1656/hpr1656_Samsung_YP-01.png
Executable file
|
After Width: | Height: | Size: 184 KiB |
BIN
eps/hpr1656/hpr1656_Samsung_YP-Z5A_1.png
Executable file
|
After Width: | Height: | Size: 234 KiB |
BIN
eps/hpr1656/hpr1656_Samsung_YP-Z5A_2.png
Executable file
|
After Width: | Height: | Size: 319 KiB |
BIN
eps/hpr1656/hpr1656_Sansa_Clip+.png
Executable file
|
After Width: | Height: | Size: 155 KiB |
BIN
eps/hpr1656/hpr1656_Sansa_Fuze.png
Executable file
|
After Width: | Height: | Size: 274 KiB |
364
eps/hpr1656/hpr1656_full_shownotes.html
Executable file
@@ -0,0 +1,364 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="my-audio-player-collection">My Audio Player Collection</h1>
|
||||||
|
<h2 id="introduction">Introduction</h2>
|
||||||
|
<p>I got broadband installed in my house in 2005 after I'd bought my first PC. I'd owned a lot of PCs before that, but they had all been cast-offs from the university I was working at, and I accessed the Internet via dial-up to my work.</p>
|
||||||
|
<p>This was around the time I got sick of listening to the radio and first discovered podcasts, and so I decided I wanted a portable audio player (or <em>MP3 Player</em> as they tended to be called back then).</p>
|
||||||
|
<p>Since then I have been listening to podcasts pretty much all of the time and have worked my way through a number of players. I thought it might be interesting if I chronicled the devices I have owned in the past 9-10 years.</p>
|
||||||
|
<h2 id="players">Players</h2>
|
||||||
|
<h3 id="iriver-ifp-899">iRiver iFP-899</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2005-03-12</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">Amazon UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">1GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£119.99</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">No</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>My first player was the iRiver iFP-899. This was a neat little device with a small monochrome screen and a joystick. It could record through an in-built microphone and played MP3 and WMA. It took a single AA battery and looked like a simple USB storage device when connected to a PC. It had a dedicated lock switch to disable the buttons, and came with a protective cover.</p>
|
||||||
|
<p>It was quite expensive at £120 but came highly recommended. I remember it being discussed on TLLTS which I had started listening to at that time, as well as being Adam (Podfather) Curry's device of choice.</p>
|
||||||
|
<p>Sadly it didn't last more than about a year and a half. The joystick accumulated dust and pocket lint and stopped working. I didn't have the skills to strip it apart and clean it out, so it went into storage (a.k.a. the junk box).</p>
|
||||||
|
<p><strong>Pictures </strong>: iRiver iFP-899 front and back<br /><img src="hpr1656_iRiver_iFP-899_1.png" alt="iRiver iFP-899 front" /> <img src="hpr1656_iRiver_iFP-899_2.png" alt="iRiver iFP-899 back" /></p>
|
||||||
|
<h3 id="samsung-yp-z5a">Samsung YP-Z5A</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2006-09-24</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">Amazon UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">4GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£99.99</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">No</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">No</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">No</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>The replacement for the iRiver was the Samsung YP-Z5A. This has a much larger capacity, is a neater shape for the pocket and plays OGG. The lack of a radio and microphone is a disadvantage but the capacity was huge in comparison to the iRiver.</p>
|
||||||
|
<p>I liked the controls on this device, and particularly the existence of a goo solid locking switch.</p>
|
||||||
|
<p>This device has developed a few problems but is still working and is still used occasionally. There has been talk of a Rockbox version but nothing seems to have come of it.</p>
|
||||||
|
<p><strong>Pictures</strong>: Samsung YP-Z5A front and back<br /><img src="hpr1656_Samsung_YP-Z5A_1.png" alt="Samsung YP-Z5A front" /> <img src="hpr1656_Samsung_YP-Z5A_2.png" alt="Samsung YP-Z5A back" /></p>
|
||||||
|
<h3 id="samsung-yp-q1">Samsung YP-Q1</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2008?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">Amazon UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">16GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">around £100</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">No</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Although this device got some good reviews it proved to be a bad buy. Firstly the controls are extremely sensitive and very difficult to use. Secondly, many of the features are only available if you connect it to the Windows <em>EmoDio</em> software, and third, the only way of accessing it through USB is via MTP.</p>
|
||||||
|
<p>The radio is good and the sound quality is great, and the device can handle MP3, OGG and FLAC. However, all in all this is not a player I would recommend.</p>
|
||||||
|
<p>Interestingly, Amazon lost my purchase details from their database for this period, so I don't have exact details. This bothers me more than it should!</p>
|
||||||
|
<p><strong>Picture</strong>: Samsung YP-Q1<br /><img src="hpr1656_Samsung_YP-01.png" alt="Samsung YP-Q1" /></p>
|
||||||
|
<h3 id="sandisk-sansa-fuze">SanDisk Sansa Fuze</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2009-07-02</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">Amazon UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">4GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£66.75</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>This is a fantastic player. I bought a hard case for it which has protected it very well. It has a great colour display and the controls are excellent. The only down-side for me is the awkwardness of the locking function - sliding the rather tiny and inaccessible power switch in reverse.</p>
|
||||||
|
<p>I was not too pleased with the native software so this was the first player on which I installed <em>Rockbox</em>. This of course turns it into an even better device capable of playing an amazingly wide range of formats.</p>
|
||||||
|
<p>The 4GB size can be extended with an SDHC card making this a large capacity player. The playlist capabilities of Rockbox make the Fuze even better.</p>
|
||||||
|
<p>The later version of the Fuze (the Fuze+) is apparently not as usable as this one, though I don't have experience of it. The Fuze model can sell for almost the same price as the original on eBay.</p>
|
||||||
|
<p><strong>Picture</strong>: Sandisk Sansa Fuze<br /><img src="hpr1656_Sansa_Fuze.png" alt="Sandisk Sansa Fuze" /></p>
|
||||||
|
<h3 id="sandisk-sansa-clip">SanDisk Sansa Clip+</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2010-11-30</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">Amazon UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">8GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£38.99</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>This is another excellent device which I originally bought to use at the gym. The screen is small and without colour but I don't see that as a problem. The controls are great, easy to use and robust. The only issue I have had with it is the fragility of the clip at the back, which broke off after only a few months.</p>
|
||||||
|
<p>The 8GB size can be extended with an SDHC card, similarly to the Fuze.</p>
|
||||||
|
<p>Rockbox can be installed on this device also, and I did this quite soon after buying it. I wish it had a dedicated locking switch, but the device can be locked under Rockbox by pressing <strong>Home</strong> and <strong>Select</strong>.</p>
|
||||||
|
<p>I have used this player for recording part of one show for HPR, and I know that Ken Fallon always uses the Clip+ as a backup recording device.</p>
|
||||||
|
<p><strong>Picture</strong>: SanDisk Sansa Clip+<br /><img src="hpr1656_Sansa_Clip+.png" alt="SanDisk Sansa Clip+" /></p>
|
||||||
|
<h3 id="iriver-h10-5gb">iRiver H10 5GB</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2013-05-28</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">eBay UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">5GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£17.25</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>I noticed that the audio players were becoming less popular and more scarce as people used their smartphones for this job, so I wanted to have several players in reserve. I looked at the Rockbox site for compatible players and started hunting for examples of them on eBay.</p>
|
||||||
|
<p>This iRiver was my first find. It is an interesting machine. Quite heavy and fairly large, with a hard disk. It has a removable battery, a colour screen and a locking switch.</p>
|
||||||
|
<p>The one I have emits a faint high-pitched noise when running, so I tend not to use it.</p>
|
||||||
|
<p><strong>Picture</strong>: iRiver H10 5GB<br /><img src="hpr1656_iRiver_H10.png" alt="iRiver H10 5GB" /></p>
|
||||||
|
<h3 id="sandisk-sansa-clip-1">SanDisk Sansa Clip+</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2013-06-02</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">eBay UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">4GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£17.28</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>Having had good experiences with the Clip+ before, and noticing their availability on eBay, I bought another one. This has been a great device, well worth the price, even though it is not new.</p>
|
||||||
|
<h3 id="apple-ipod-mini-2nd-generation">Apple iPod mini 2nd Generation</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2013-06-03</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">eBay UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">4GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£16.46</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>I wanted to see what this model iPod was like, but having acquired it I find I really dislike this device for reasons I'm not entirely sure about. Rockbox improves it to some degree, but the hardware seems poor in comparison to the Sansa Fuze for example. I would not recommend this player given the availability of the alternatives.</p>
|
||||||
|
<p><strong>Picture</strong>: Apple iPod mini 2nd Generation<br /><img src="hpr1656_iPod_mini.png" alt="Apple iPod mini 2nd Generation" /></p>
|
||||||
|
<h3 id="iriver-h10-6gb">iRiver H10 6GB</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">When purchased</td>
|
||||||
|
<td style="text-align: left;">2013-06-15</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Vendor</td>
|
||||||
|
<td style="text-align: left;">eBay UK</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Capacity</td>
|
||||||
|
<td style="text-align: left;">6GB</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Cost</td>
|
||||||
|
<td style="text-align: left;">£11.37</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">FM Tuner</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;">Microphone?</td>
|
||||||
|
<td style="text-align: left;">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;">Rockbox capable?</td>
|
||||||
|
<td style="text-align: left;">Perhaps</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>This device seems identical to the 5GB model. However, on trying to install Rockbox I could not get it to run.</p>
|
||||||
|
<p>Researching this model (rather too late) I found that other people had also had issues with it and Rockbox, so I suspect there may be an issue with this particular configuration.</p>
|
||||||
|
<h3 id="other-purchases">Other purchases</h3>
|
||||||
|
<p>Having had such great experiences with the Sansa Fuze and Clip+ I decided to collect a few more as backup devices should the others fail. I bought three Sansa Fuze players and one Sansa Clip+.</p>
|
||||||
|
<p>This turned out to be a bit of a mixed experience. The Clip+ was purchased from Play.com as a manufacturer refurbished player, and has been absolutely superb. However, two of the three Fuze players gave problems. They were all bought from eBay, but I think that two were version 1 hardware and were running firmware versions which begin with '01'. These players seem unreliable and randomly dismount themselves when plugged in to a PC downloading media. The third Fuze seems good, and seems to be a version 2 device with '02' version firmware.</p>
|
||||||
|
<p>I am very glad to have these players. I have an Android smartphone, a fairly recent purchase, but it's too big and heavy for a shirt pocket, and is a lot less convenient than a Sansa Fuze or Clip+. Your mileage may vary of course!</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Rockbox <a href="http://www.rockbox.org/">http://www.rockbox.org/</a></li>
|
||||||
|
<li>SanDisk Sansa range <a href="http://en.wikipedia.org/wiki/SanDisk_Sansa">http://en.wikipedia.org/wiki/SanDisk_Sansa</a></li>
|
||||||
|
<li>iRiver H10 series <a href="http://en.wikipedia.org/wiki/Iriver_H10_series">http://en.wikipedia.org/wiki/Iriver_H10_series</a></li>
|
||||||
|
<li>Apple iPod mini <a href="http://en.wikipedia.org/wiki/IPod_Mini">http://en.wikipedia.org/wiki/IPod_Mini</a></li>
|
||||||
|
</ul>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
BIN
eps/hpr1656/hpr1656_iPod_mini.png
Executable file
|
After Width: | Height: | Size: 293 KiB |
BIN
eps/hpr1656/hpr1656_iRiver_H10.png
Executable file
|
After Width: | Height: | Size: 301 KiB |
BIN
eps/hpr1656/hpr1656_iRiver_iFP-899_1.png
Executable file
|
After Width: | Height: | Size: 297 KiB |
BIN
eps/hpr1656/hpr1656_iRiver_iFP-899_2.png
Executable file
|
After Width: | Height: | Size: 452 KiB |
283
eps/hpr1694/hpr1694_full_shownotes.html
Executable file
@@ -0,0 +1,283 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="my-apod-downloader">My APOD Downloader</h1>
|
||||||
|
<h2 id="astronomy-picture-of-the-day">Astronomy Picture of the Day</h2>
|
||||||
|
<p>You have probably heard of the <a href="http://apod.nasa.gov/apod/astropix.html">Astronomy Picture of the Day (APOD)</a> site. It has existed since 1995, is provided by <a href="http://en.wikipedia.org/wiki/NASA">NASA</a> and <a href="http://en.wikipedia.org/wiki/Michigan_Technological_University">Michigan Technological University (MTU)</a> and is created and managed by <a href="http://www.mtu.edu/physics/department/faculty/nemiroff/">Robert Nemiroff</a> and <a href="http://antwrp.gsfc.nasa.gov/htmltest/jbonnell/www/bonnell.html">Jerry Bonnell</a>. The FAQ on the site says <em>"The APOD archive contains the largest collection of annotated astronomical images on the internet"</em>.</p>
|
||||||
|
<h2 id="the-downloader">The Downloader</h2>
|
||||||
|
<p>Being a KDE user I quite like a moderate amount of bling, and I particularly like to have a picture on my desktop. I like to rotate my wallpaper pictures every so often, so I want to have a collection of images. To this end I download the APOD on my server every day and make the images available through an NFS-mounted volume.</p>
|
||||||
|
<p>In 2012 I wrote a Perl script to perform the download, using a fairly primitive HTML parsing method. This script has been improved over the intervening years and now uses the Perl module <a href="http://search.cpan.org/~cjm/HTML-Tree-5.03/lib/HTML/TreeBuilder.pm"><code>HTML::TreeBuilder</code></a> which I believe is much better at parsing HTML.</p>
|
||||||
|
<p>The version of the script I use myself also includes the Perl module <code>Image::Magick</code> which interfaces to the awesome <a href="http://www.imagemagick.org/"><code>ImageMagick</code></a> image manipulation software suite. I use this to annotate the downloaded image with the title parsed from the HTML so I know what it is.</p>
|
||||||
|
<p>The script I am presenting here is called <code>collect_apod_simple</code> and does not use <code>ImageMagick</code>. I chose to omit it because the installation of this suite and the related Perl module can be difficult. Also, I do not feel that the annotation always works as well as it could, and I have not yet found the time to correct this shortcoming.</p>
|
||||||
|
<p>A version of the more advanced script (called <code>collect_apod</code>) is available in the same place as <code>collect_apod_simple</code> should you wish to give it a try. Both scripts are available on <em>GitLab</em> under the link <a href="https://gitlab.com/davmo/hprmisc" class="uri">https://gitlab.com/davmo/hprmisc</a>.</p>
|
||||||
|
<h2 id="the-code">The Code</h2>
|
||||||
|
<p>If you are acquainted with Perl you'll probably find this script quite simple. All it really does is:</p>
|
||||||
|
<ul>
|
||||||
|
<li><p>Get or compute the date string for building the APOD URL</p></li>
|
||||||
|
<li><p>Download the HTML on the selected APOD page</p></li>
|
||||||
|
<li><p>Look for an image being used as a link</p></li>
|
||||||
|
<li><p>Download the image being linked to and save it where requested</p></li>
|
||||||
|
</ul>
|
||||||
|
<p>The following is a numbered listing with annotations. There are a several comments in the script itself, but the annotations are there to try and make the various sections as clear as possible.</p>
|
||||||
|
<pre><code> 1 #!/usr/bin/env perl
|
||||||
|
2 #===============================================================================
|
||||||
|
3 #
|
||||||
|
4 # FILE: collect_apod_simple
|
||||||
|
5 #
|
||||||
|
6 # USAGE: ./collect_apod_simple [YYMMDD]
|
||||||
|
7 #
|
||||||
|
8 # DESCRIPTION: Downloads the current Astronomy Picture of the Day or that
|
||||||
|
9 # relating to the formatted date provided as an argument. In
|
||||||
|
10 # this context "current" can mean two URLs: .../astropix.html or
|
||||||
|
11 # .../apYYMMDD.html. We now *do not* download the
|
||||||
|
12 # .../astropix.html version since it has a different HTML
|
||||||
|
13 # layout.
|
||||||
|
14 #
|
||||||
|
15 # OPTIONS: ---
|
||||||
|
16 # REQUIREMENTS: ---
|
||||||
|
17 # BUGS: ---
|
||||||
|
18 # NOTES: Based on 'collect_apod' but without the Image::Magick stuff,
|
||||||
|
19 # for simplicity and for release to the HPR community
|
||||||
|
20 # AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
|
||||||
|
21 # VERSION: 0.0.1
|
||||||
|
22 # CREATED: 2015-01-02 19:58:01
|
||||||
|
23 # REVISION: 2015-01-03 23:00:27
|
||||||
|
24 #
|
||||||
|
25 #===============================================================================
|
||||||
|
26
|
||||||
|
27 use 5.010;
|
||||||
|
28 use strict;
|
||||||
|
29 use warnings;
|
||||||
|
30 use utf8;
|
||||||
|
31
|
||||||
|
32 use LWP::UserAgent;
|
||||||
|
33 use DateTime;
|
||||||
|
34 use HTML::TreeBuilder 5 -weak;
|
||||||
|
35 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 32-34 define the modules the script uses:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="http://search.cpan.org/dist/libwww-perl/lib/LWP/UserAgent.pm"><code>LWP::UserAgent</code></a> used to perform the web downloads</li>
|
||||||
|
<li><a href="http://search.cpan.org/~drolsky/DateTime-1.18/lib/DateTime.pm"><code>DateTime</code></a> generates and formats the default date</li>
|
||||||
|
<li><a href="http://search.cpan.org/~cjm/HTML-Tree-5.03/lib/HTML/TreeBuilder.pm"><code>HTML::TreeBuilder</code></a> parses HTML</li>
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 36 #
|
||||||
|
37 # Version number (manually incremented)
|
||||||
|
38 #
|
||||||
|
39 our $VERSION = '0.0.1';
|
||||||
|
40
|
||||||
|
41 #
|
||||||
|
42 # Set to 0 to be more silent
|
||||||
|
43 #
|
||||||
|
44 my $DEBUG = 1;
|
||||||
|
45
|
||||||
|
46 #
|
||||||
|
47 # Script name
|
||||||
|
48 #
|
||||||
|
49 ( my $PROG = $0 ) =~ s|.*/||mx;
|
||||||
|
50
|
||||||
|
51 #-------------------------------------------------------------------------------
|
||||||
|
52 # Edit this to your needs
|
||||||
|
53 #-------------------------------------------------------------------------------
|
||||||
|
54 #
|
||||||
|
55 # Where the script will download the picture. Edit this to where you want
|
||||||
|
56 #
|
||||||
|
57 my $image_base = "$ENV{HOME}/Backgrounds/apod";
|
||||||
|
58
|
||||||
|
59 #-------------------------------------------------------------------------------
|
||||||
|
60 # Nothing needs editing below here
|
||||||
|
61 #-------------------------------------------------------------------------------
|
||||||
|
62
|
||||||
|
63 #
|
||||||
|
64 # Get the argument or default it
|
||||||
|
65 #
|
||||||
|
66 my $arg = shift;
|
||||||
|
67 unless ( defined($arg) ) {
|
||||||
|
68 #
|
||||||
|
69 # APOD wants a date in YYMMDD format
|
||||||
|
70 #
|
||||||
|
71 my $dt = DateTime->now;
|
||||||
|
72 $arg = sprintf( "%02i%02i%02i",
|
||||||
|
73 substr( $dt->year, -2 ),
|
||||||
|
74 $dt->month, $dt->day );
|
||||||
|
75 }
|
||||||
|
76
|
||||||
|
77 #
|
||||||
|
78 # Check the argument is a valid date in YYMMDD format
|
||||||
|
79 #
|
||||||
|
80 die "Usage: $PROG [YYMMDD]\n" unless ( $arg =~ /^\d{6}$/ );
|
||||||
|
81 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 66-80 collect the date from the command line, or if none is given generate the correctly formatted date. If a date in an invalid format is given the script aborts.</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 82 #
|
||||||
|
83 # Make an URL depending on the argument
|
||||||
|
84 #
|
||||||
|
85 my $apod_base = "http://apod.nasa.gov/apod";
|
||||||
|
86 my $apod_URL = "$apod_base/ap$arg.html";
|
||||||
|
87 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 85-86 define the APOD URL for the chosen date. This will look like http://apod.nasa.gov/apod/ap150106.html for 2015-01-06 for example.</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 88 #
|
||||||
|
89 # General declarations
|
||||||
|
90 #
|
||||||
|
91 my ( $image_URL, $image_file );
|
||||||
|
92 my ( $tree, $title );
|
||||||
|
93 my ( $url, $element, $attr, $tag );
|
||||||
|
94
|
||||||
|
95 #
|
||||||
|
96 # Enable Unicode mode
|
||||||
|
97 #
|
||||||
|
98 binmode STDOUT, ":encoding(UTF-8)";
|
||||||
|
99 binmode STDERR, ":encoding(UTF-8)";
|
||||||
|
100
|
||||||
|
101 if ($DEBUG) {
|
||||||
|
102 print "Base URL: $apod_base\n";
|
||||||
|
103 print "APOD URL: $apod_URL\n";
|
||||||
|
104 print "Image base: $image_base\n";
|
||||||
|
105 print "\n";
|
||||||
|
106 }
|
||||||
|
107
|
||||||
|
108 #
|
||||||
|
109 # Get the HTML page, pretending to be some unknown User Agent
|
||||||
|
110 #
|
||||||
|
111 my $ua = LWP::UserAgent->new;
|
||||||
|
112 $ua->agent("MyApp/0.1");
|
||||||
|
113
|
||||||
|
114 my $req = HTTP::Request->new( GET => $apod_URL );
|
||||||
|
115
|
||||||
|
116 my $res = $ua->request($req);
|
||||||
|
117 if ( $res->is_success ) {
|
||||||
|
118 print "GET request successful\n" if $DEBUG;
|
||||||
|
119
|
||||||
|
120 #
|
||||||
|
121 # Parse the HTML we got back
|
||||||
|
122 #
|
||||||
|
123 $tree = HTML::TreeBuilder->new;
|
||||||
|
124 $tree->parse_content( $res->content_ref );
|
||||||
|
125 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 111-114 set up and download the APOD web page. If the download was successful then the HTML is parsed with HTML::TreeBuilder in lines 123 and 124.</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 126 #
|
||||||
|
127 # Get and display the title in debug mode
|
||||||
|
128 #
|
||||||
|
129 if ($DEBUG) {
|
||||||
|
130 if ( $title = $tree->look_down( _tag => 'title' ) ) {
|
||||||
|
131 $title = $title->as_trimmed_text();
|
||||||
|
132 print "Found title: $title\n" if $title;
|
||||||
|
133 }
|
||||||
|
134 }
|
||||||
|
135
|
||||||
|
136 #
|
||||||
|
137 # Look for the image. This is expected to be the href attribute of an <a>
|
||||||
|
138 # tag. The image we see on the page is merely a link to this (usually)
|
||||||
|
139 # larger image.
|
||||||
|
140 #
|
||||||
|
141 for ( @{ $tree->extract_links('a') } ) {
|
||||||
|
142 ( $url, $element, $attr, $tag ) = @$_;
|
||||||
|
143 if ($DEBUG) {
|
||||||
|
144 print "Found: $url\n" if $url;
|
||||||
|
145 }
|
||||||
|
146 last unless defined($url);
|
||||||
|
147 last if ( $url =~ /\.(jpg|png)$/i );
|
||||||
|
148 }
|
||||||
|
149 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 141-148 consist of a loop which walks through the parsed HTML looking for <a> tags. The loop ends if the tag references an image URL.</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 150 #
|
||||||
|
151 # Abort if no image (it might be a video or a GIF)
|
||||||
|
152 #
|
||||||
|
153 die "Image URL not found\n"
|
||||||
|
154 unless defined($url)
|
||||||
|
155 && $url =~ /\.(jpg|png)$/i;
|
||||||
|
156 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 153-155 check that an image URL was actually found. Some days the APOD site might host a YouTube video or some other animated display. The script is not interested in these since they are no use as wallpaper.</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 157 $image_URL = "$apod_base/$url";
|
||||||
|
158
|
||||||
|
159 #
|
||||||
|
160 # Extract the final part of the URL for the file name. We usually get
|
||||||
|
161 # a JPEG, sometimes with a shouty extension, which we change.
|
||||||
|
162 #
|
||||||
|
163 ( $image_file = $image_URL ) =~ s|.*/||mx;
|
||||||
|
164 ( $image_file = "$image_base/$image_file" ) =~ s/JPG$/jpg/mx;
|
||||||
|
165
|
||||||
|
166 if ($DEBUG) {
|
||||||
|
167 print "Image URL: $image_URL\n";
|
||||||
|
168 print "Image file: $image_file\n";
|
||||||
|
169 }
|
||||||
|
170
|
||||||
|
171 #
|
||||||
|
172 # Abort if the file already exists (the script already ran?)
|
||||||
|
173 #
|
||||||
|
174 die "File $image_file already exists\n" if ( -f $image_file );
|
||||||
|
175 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 157-174 prepare the image URL and make a file name to hold the image.</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 176 #
|
||||||
|
177 # Set up the GET request for the image
|
||||||
|
178 #
|
||||||
|
179 $req = HTTP::Request->new( GET => $image_URL );
|
||||||
|
180
|
||||||
|
181 #
|
||||||
|
182 # Download the image to the (possibly renamed) image file
|
||||||
|
183 #
|
||||||
|
184 $res = $ua->request( $req, $image_file );
|
||||||
|
185 if ( $res->is_success ) {
|
||||||
|
186 print "Downloaded to $image_file\n" if $DEBUG;
|
||||||
|
187 }
|
||||||
|
188 else {
|
||||||
|
189 #
|
||||||
|
190 # The image download failed
|
||||||
|
191 #
|
||||||
|
192 die $res->status_line, " ($image_URL)\n";
|
||||||
|
193 }
|
||||||
|
194 </code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Lines 179-193 download the image to a file</p>
|
||||||
|
<hr />
|
||||||
|
<pre><code> 195 }
|
||||||
|
196 else {
|
||||||
|
197 #
|
||||||
|
198 # We failed to get the web page
|
||||||
|
199 #
|
||||||
|
200 die $res->status_line, " ($apod_URL)\n";
|
||||||
|
201 }
|
||||||
|
202
|
||||||
|
203 exit;
|
||||||
|
204
|
||||||
|
205 # vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker</code></pre>
|
||||||
|
<p>I hope you find the script interesting and/or useful.</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Wikipedia entry <a href="http://en.wikipedia.org/wiki/Astronomy_Picture_of_the_Day" class="uri">http://en.wikipedia.org/wiki/Astronomy_Picture_of_the_Day</a></li>
|
||||||
|
<li>Astronomy Picture of the Day <a href="http://apod.nasa.gov/apod/astropix.html" class="uri">http://apod.nasa.gov/apod/astropix.html</a></li>
|
||||||
|
<li>NASA <a href="http://en.wikipedia.org/wiki/NASA" class="uri">http://en.wikipedia.org/wiki/NASA</a></li>
|
||||||
|
<li>Michigan Technological University (MTU) <a href="http://en.wikipedia.org/wiki/Michigan_Technological_University" class="uri">http://en.wikipedia.org/wiki/Michigan_Technological_University</a></li>
|
||||||
|
<li>Robert Nemiroff <a href="http://www.mtu.edu/physics/department/faculty/nemiroff/" class="uri">http://www.mtu.edu/physics/department/faculty/nemiroff/</a></li>
|
||||||
|
<li>Jerry Bonnell <a href="http://antwrp.gsfc.nasa.gov/htmltest/jbonnell/www/bonnell.html" class="uri">http://antwrp.gsfc.nasa.gov/htmltest/jbonnell/www/bonnell.html</a></li>
|
||||||
|
<li><code>HTML::TreeBuilder</code> Perl module <a href="http://search.cpan.org/~cjm/HTML-Tree-5.03/lib/HTML/TreeBuilder.pm" class="uri">http://search.cpan.org/~cjm/HTML-Tree-5.03/lib/HTML/TreeBuilder.pm</a></li>
|
||||||
|
<li><code>ImageMagick</code> image manipulation software suite <a href="http://www.imagemagick.org/" class="uri">http://www.imagemagick.org/</a></li>
|
||||||
|
<li><em>GitLab</em> link <a href="https://gitlab.com/davmo/hprmisc" class="uri">https://gitlab.com/davmo/hprmisc</a>.</li>
|
||||||
|
</ul>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
281
eps/hpr1734/hpr1734_full_shownotes.html
Executable file
@@ -0,0 +1,281 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Vim Hints 003 (HPR Show 1734)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Vim Hints 003 (HPR Show 1734)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#moving-around">Moving Around</a><ul>
|
||||||
|
<li><a href="#simple-movement">Simple movement</a></li>
|
||||||
|
<li><a href="#word-related-movement">Word-related movement</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#more-configuration-settings">More configuration settings</a><ul>
|
||||||
|
<li><a href="#adding-a-ruler">Adding a ruler</a></li>
|
||||||
|
<li><a href="#adding-a-status-line">Adding a status line</a></li>
|
||||||
|
<li><a href="#showing-the-mode">Showing the mode</a></li>
|
||||||
|
<li><a href="#adding-comments">Adding comments</a></li>
|
||||||
|
<li><a href="#screenshot">Screenshot</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#summary">Summary</a></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<p>In this episode I want to look at how you move around the file you are editing in Vim. I also want to add some more elements to the configuration file we started building in the <a href="http://hackerpublicradio.org/eps.php?id=1724" title="Vim Hints 002">last episode</a>.</p>
|
||||||
|
<h2 id="moving-around">Moving Around</h2>
|
||||||
|
<p>One of the powerful features of Vim is the ease with which you can move around a file.</p>
|
||||||
|
<h3 id="simple-movement">Simple movement</h3>
|
||||||
|
<p>Some of the basic movements in <em>Normal</em> mode are:</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th style="text-align: center;">Key</th>
|
||||||
|
<th style="text-align: left;">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>l</strong> or <strong>cursor-right</strong></td>
|
||||||
|
<td style="text-align: left;">Move right</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>k</strong> or <strong>cursor-up</strong></td>
|
||||||
|
<td style="text-align: left;">Move up</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>j</strong> or <strong>cursor-down</strong></td>
|
||||||
|
<td style="text-align: left;">Move down</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>h</strong> or <strong>cursor-left</strong></td>
|
||||||
|
<td style="text-align: left;">Move left</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>$</strong> or <strong>End key</strong></td>
|
||||||
|
<td style="text-align: left;">Move to the end of the line</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>0</strong> or <strong>Home key</strong></td>
|
||||||
|
<td style="text-align: left;">Move to the start of the line</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>^</strong></td>
|
||||||
|
<td style="text-align: left;">Move to the first non-blank character of the line</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>-</strong></td>
|
||||||
|
<td style="text-align: left;">Move up to first non-blank character</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>+</strong></td>
|
||||||
|
<td style="text-align: left;">Move down to first non-blank character</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><strong>Note</strong>: In the Vim documentation there is an alternative annotation for these keys (and many others):</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th style="text-align: left;">Vim Annotation</th>
|
||||||
|
<th style="text-align: center;">Key</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;"><Up></td>
|
||||||
|
<td style="text-align: center;">cursor-up</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;"><Down></td>
|
||||||
|
<td style="text-align: center;">cursor-down</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;"><Left></td>
|
||||||
|
<td style="text-align: center;">cursor-left</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;"><Right></td>
|
||||||
|
<td style="text-align: center;">cursor-right</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: left;"><Home></td>
|
||||||
|
<td style="text-align: center;">home</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: left;"><End></td>
|
||||||
|
<td style="text-align: center;">end</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>We will use this form of annotation in these and future notes. These will also be important when we look at customisation.</p>
|
||||||
|
<p>If a key is used in conjunction with the <strong>Shift</strong> or <strong>Control</strong> (<strong>CTRL</strong>) keys the annotation is shown as <strong><S-Right></strong> (shift + cursor-right) or <strong><C-Right></strong> (CTRL + cursor-right).</p>
|
||||||
|
<p>Some of these motion commands hardly seem different from what is available in other editors. Many presses of the right cursor key will move the cursor to the right a number of columns in most editors. However, Vim allows these keys to be preceded by a number. So, typing:</p>
|
||||||
|
<pre><code>10l</code></pre>
|
||||||
|
<p>will move the cursor 10 characters to the right, as will <strong>10<Right></strong>.</p>
|
||||||
|
<p>The same goes for <strong>10h</strong> or <strong>10<Left></strong>, <strong>10k</strong> or <strong>10<Up></strong> and so forth.</p>
|
||||||
|
<p>The only movement commands in this group which do not take a count are <strong>0</strong> / <strong><Home></strong> and <strong>^</strong>.</p>
|
||||||
|
<h3 id="word-related-movement">Word-related movement</h3>
|
||||||
|
<p>The next movement commands (used in <em>Normal</em> mode) move the cursor in relation to words in the text. There two definitions of "<em>word</em>" in this context. We will use the Vim convention in these notes and refer to them as <em><code>word</code></em> and <em><code>WORD</code></em>.</p>
|
||||||
|
<p>These are the definitions from the Vim documentation:</p>
|
||||||
|
<p><em><code>word</code></em> : a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs, end of line). An empty line is also considered to be a <em><code>word</code></em>.</p>
|
||||||
|
<p><em><code>WORD</code></em> : a sequence of non-blank characters, separated with white space. An empty line is also considered to be a <em><code>WORD</code></em>.</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th style="text-align: center;">Key</th>
|
||||||
|
<th style="text-align: left;">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>w</strong> or <strong><S-Right></strong></td>
|
||||||
|
<td style="text-align: left;">Move forward to the start of a <em><code>word</code></em></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>W</strong> or <strong><C-Right></strong></td>
|
||||||
|
<td style="text-align: left;">Move forward to the start of a <em><code>WORD</code></em></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>e</strong></td>
|
||||||
|
<td style="text-align: left;">Move forward to the end of a <em><code>word</code></em></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>E</strong></td>
|
||||||
|
<td style="text-align: left;">Move forward to the end of a <em><code>WORD</code></em></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>b</strong> or <strong><S-Left></strong></td>
|
||||||
|
<td style="text-align: left;">Move backward to the start of a <em><code>word</code></em></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>B</strong> or <strong><C-Left></strong></td>
|
||||||
|
<td style="text-align: left;">Move backward to the start of a <em><code>WORD</code></em></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>These movement commands may be preceded by a numeric count, as before, so <strong>5w</strong> or <strong>5<S-Right></strong> will move the cursor forward by 5 words to the start of the 6th word from the current position.</p>
|
||||||
|
<p>The following list shows the effects of various word-related movements moving the cursor along the example log record. It contrasts the use of <em><code>word</code></em> versus <em><code>WORD</code></em> commands. The <strong>^</strong> characters represent the cursor positions after the various commands. All commands begin moving from the <strong>F</strong> of <em>FAT</em>. The last two move to the right 80 columns then backwards.</p>
|
||||||
|
<pre><code> FAT-fs (sdh): utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive!
|
||||||
|
^
|
||||||
|
5w ^
|
||||||
|
5W ^
|
||||||
|
7e ^
|
||||||
|
7E ^
|
||||||
|
80l5b ^
|
||||||
|
80l5B ^</code></pre>
|
||||||
|
<p>If this is unclear then here are the effects of these commands in text form:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>5w</strong> moves forward to the closing bracket</li>
|
||||||
|
<li><strong>5W</strong> moves forward to the first lower case '<em>a</em>'</li>
|
||||||
|
<li><strong>7e</strong> moves forward to the '<em>8</em>'</li>
|
||||||
|
<li><strong>7E</strong> moves forward to the last '<em>d</em>' of '<em>recommended</em>'</li>
|
||||||
|
<li><strong>80l5b</strong> moves forward 80 columns to the right to the second '<em>e</em>' of '<em>filesystem</em>' then backwards to the '<em>f</em>' of '<em>for</em>'</li>
|
||||||
|
<li><strong>80l5B</strong> moves forward 80 columns to the right then backwards to the '<em>c</em>' of '<em>charset</em>'</li>
|
||||||
|
</ul>
|
||||||
|
<p>There are many more movement commands which we will look at in forthcoming episodes.</p>
|
||||||
|
<hr />
|
||||||
|
<h2 id="more-configuration-settings">More configuration settings</h2>
|
||||||
|
<p>In the <a href="http://hackerpublicradio.org/eps.php?id=1724" title="Vim Hints 002">last episode</a> we looked at some of the basic elements of the configuration file. So far the file contains the following:</p>
|
||||||
|
<pre><code>set compatible
|
||||||
|
set backup
|
||||||
|
set undodir=~/.vim/undodir
|
||||||
|
set undofile</code></pre>
|
||||||
|
<p>We can now add some more settings.</p>
|
||||||
|
<h3 id="adding-a-ruler">Adding a ruler</h3>
|
||||||
|
<p>Vim will display a ruler at the bottom of the screen if this option is enabled. Add the following to the configuration file:</p>
|
||||||
|
<pre><code>set ruler</code></pre>
|
||||||
|
<p>This causes the line and column number of the cursor position to be shown at the bottom right of the screen, separated by a comma. When there is room, the relative position of the displayed text in the file is shown on the far right.</p>
|
||||||
|
<p>The relative position is <em>Top</em> when the first line of the file is visible, <em>Bot</em> when the last line is visible, <em>All</em> when both top and bottom lines are visible, and if none of the foregoing, <em>N%</em>, the relative position in the file.</p>
|
||||||
|
<p>The command can be abbreviated to <strong>se ru</strong>. I prefer to use the full form because it is easier to remember what it means!</p>
|
||||||
|
<p>The ruler can also be turned off with <strong>set noruler</strong> which you would prefix with a colon while in a Vim editing session to enter <em>command mode</em>:</p>
|
||||||
|
<pre><code>:set noruler</code></pre>
|
||||||
|
<p>It is possible to customise the contents of the ruler, but we will not be looking at this for the moment.</p>
|
||||||
|
<p>Note that some Linux distributions set this option for you. I run <em>Debian Testing</em> and a <strong>set ruler</strong> definition can be found in <strong>/usr/share/vim/vim74/debian.vim</strong>. It is a good idea to set it in your configuration file regardless, however, because you might need to transfer this file to another distribution in the future,</p>
|
||||||
|
<h3 id="adding-a-status-line">Adding a status line</h3>
|
||||||
|
<p>By default the Vim window uses the whole terminal window except for the last line, as we saw in <a href="http://hackerpublicradio.org/eps.php?id=1714" title="Vim Hints 001">episode 1</a>. The last line is used for displaying various messages, and the ruler, and for entering "<strong>:</strong>" commands.</p>
|
||||||
|
<p>It is possible to separate the status information from the command entry line with the following option:</p>
|
||||||
|
<pre><code>set laststatus=2</code></pre>
|
||||||
|
<p>This creates an inverse colour status line at the bottom of the screen followed by the command entry line. The status line contains the name of the file being edited, and the ruler (if enabled). The final line contains messages and is where commands are entered.</p>
|
||||||
|
<p>If the terminal you are using is small (like the 24 line by 80 column hardware terminals Vi was originally written for), stealing these lines from the Vim workspace may be a problem. In today's world it's unlikely to be so, and I always enable these.</p>
|
||||||
|
<p>This command can be abbreviated to <strong>se ls=2</strong>.</p>
|
||||||
|
<p>The status line can also be turned off with <strong>set laststatus=0</strong>.</p>
|
||||||
|
<h3 id="showing-the-mode">Showing the mode</h3>
|
||||||
|
<p>As we have seen, Vim is a modal editor with several modes, some of which we have yet to look at. By default, Vim does not indicate which mode it is in, but the following command in the configuration file will change this:</p>
|
||||||
|
<pre><code>set showmode</code></pre>
|
||||||
|
<p>As with <strong>set ruler</strong>, some Linux distributions set this for you, but I believe in setting this myself.</p>
|
||||||
|
<p>With <em>showmode</em> enabled a message such as <code>-- INSERT --</code> (<em>insert</em> mode) is shown on the last line of the Vim window.</p>
|
||||||
|
<p>This command can be abbreviated to <strong>se smd</strong>.</p>
|
||||||
|
<p>The mode display can also be turned off with <strong>set noshowmode</strong> which you would prefix with a colon while in a Vim editing session to enter <em>command mode</em>:</p>
|
||||||
|
<pre><code>:set noshowmode</code></pre>
|
||||||
|
<h3 id="adding-comments">Adding comments</h3>
|
||||||
|
<p>The comment character used by Vim in configuration files and elsewhere is the double quote character '<strong>"</strong>'. See the summary below for an example.</p>
|
||||||
|
<h3 id="screenshot">Screenshot</h3>
|
||||||
|
<p>The following screenshot shows Vim in an <em>xterm</em> window (24x80) editing the notes for this episode (written in enhanced <em>Markdown</em>, to be processed with <em>pandoc</em>). The configuration file used is the same as that shown below in the summary.</p>
|
||||||
|
<p><img src="hpr1734_img001.png" alt="Vim screenshot" /><br />Picture: Vim with ruler and status line</p>
|
||||||
|
<h2 id="summary">Summary</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Movement
|
||||||
|
<ul>
|
||||||
|
<li><strong>h</strong>, <strong>j</strong>, <strong>k</strong>, <strong>l</strong> or cursor keys</li>
|
||||||
|
<li><strong>$</strong> or <strong><End></strong></li>
|
||||||
|
<li><strong>0</strong> or <strong><Home></strong></li>
|
||||||
|
<li><strong>^</strong>, <strong>-</strong> and <strong>+</strong></li>
|
||||||
|
<li><strong>w</strong> or <strong><S-Right></strong>, <strong>W</strong> or <strong><C-Right></strong></li>
|
||||||
|
<li><strong>e</strong>, <strong>E</strong></li>
|
||||||
|
<li><strong>b</strong> or <strong><S-Left></strong>, <strong>B</strong> or <strong><C-Left></strong></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Configuration file - this time with comments</li>
|
||||||
|
</ul>
|
||||||
|
<pre><code>" Ensure Vim runs as Vim
|
||||||
|
set nocompatible
|
||||||
|
|
||||||
|
" Keep a backup file
|
||||||
|
set backup
|
||||||
|
|
||||||
|
" Keep change history
|
||||||
|
set undodir=~/.vim/undodir
|
||||||
|
set undofile
|
||||||
|
|
||||||
|
" Show the line,column and the % of buffer
|
||||||
|
set ruler
|
||||||
|
|
||||||
|
" Always show a status line per window
|
||||||
|
set laststatus=2
|
||||||
|
|
||||||
|
" Show Insert, Replace or Visual on the last line
|
||||||
|
set showmode</code></pre>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ol type="1">
|
||||||
|
<li>Vim Hints Episode 1 <a href="http://hackerpublicradio.org/eps.php?id=1714">http://hackerpublicradio.org/eps.php?id=1714</a></li>
|
||||||
|
<li>Vim Hints Episode 2 <a href="http://hackerpublicradio.org/eps.php?id=1724">http://hackerpublicradio.org/eps.php?id=1724</a></li>
|
||||||
|
</ol>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1734/hpr1734_img001.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
eps/hpr1740/hpr1740_full_shownotes.epub
Executable file
214
eps/hpr1740/hpr1740_full_shownotes.html
Executable file
@@ -0,0 +1,214 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Mailing List Etiquette (HPR Show 1740)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Mailing List Etiquette (HPR Show 1740)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#overview">Overview</a></li>
|
||||||
|
<li><a href="#list-etiquette-summary">List Etiquette Summary</a></li>
|
||||||
|
<li><a href="#threads">Threads</a></li>
|
||||||
|
<li><a href="#how-email-threads-work">How Email Threads Work</a><ul>
|
||||||
|
<li><a href="#the-message-id-header-field">The "Message-ID:" Header Field</a></li>
|
||||||
|
<li><a href="#the-in-reply-to-and-references-header-fields">The "In-Reply-To:" and "References:" Header Fields</a></li>
|
||||||
|
<li><a href="#so-what-is-a-thread-then">So, what is a thread then?</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#list-etiquette">List Etiquette</a><ul>
|
||||||
|
<li><a href="#threads-1">Threads</a><ul>
|
||||||
|
<li><a href="#use-reply-in-your-mail-client">Use "Reply" in your mail client</a></li>
|
||||||
|
<li><a href="#do-not-change-the-subject-line">Do not change the "Subject" line</a></li>
|
||||||
|
<li><a href="#do-not-try-to-start-a-new-thread-by-replying-to-an-old-one">Do not try to start a new thread by replying to an old one</a></li>
|
||||||
|
<li><a href="#do-not-start-a-new-thread-to-reply-to-an-existing-one">Do not start a new thread to reply to an existing one</a></li>
|
||||||
|
<li><a href="#do-not-reply-to-digest-messages">Do not reply to digest messages</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#formatting-replies">Formatting replies</a><ul>
|
||||||
|
<li><a href="#quote-the-text-you-are-replying-to">Quote the text you are replying to</a></li>
|
||||||
|
<li><a href="#trim-the-text-you-are-replying-to">Trim the text you are replying to</a></li>
|
||||||
|
<li><a href="#do-not-top-post">Do not top post</a></li>
|
||||||
|
<li><a href="#use-an-email-client-that-can-do-the-right-thing">Use an email client that can do the right thing!</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
<p>In February 2015 I created a script to add a section to the monthly Community News show notes. The added section summarises the discussions on the HPR mailing list over the previous month. My script processes the messages archived on the Gmane site and reports on the <em><a href="http://en.wikipedia.org/wiki/Conversation_threading" title="threads">threads</a></em> it finds there.</p>
|
||||||
|
<p>In writing this script I noticed the number of times people made errors in replying to existing message threads and initiating new threads on the list. I thought it might be helpful if I explained some of the <em><a href="http://www.visualthesaurus.com/cm/wc/dos-and-donts-or-dos-and-donts/" title="do's and don'ts">do's and don'ts</a></em> of mailing list use to help avoid these errors.</p>
|
||||||
|
<h2 id="list-etiquette-summary">List Etiquette Summary</h2>
|
||||||
|
<p>Since this document is long I have included a brief summary here.</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Threads</strong> - keep related messages together in time order
|
||||||
|
<ul>
|
||||||
|
<li><em>Use "Reply" in your mail client</em> - it knows how to do threading properly
|
||||||
|
<ul>
|
||||||
|
<li><em>Use Reply to List or Reply To All</em> - or the list or sender might not get a copy</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><em>Do not change the "Subject" line</em> - make a new thread for a new subject</li>
|
||||||
|
<li><em>Do not try to start a new thread by replying to an old one</em> - make a new thread for a new subject</li>
|
||||||
|
<li><em>Do not start a new thread to reply to an existing one</em> - just use "Reply"</li>
|
||||||
|
<li><em>Do not reply to digest messages</em> - digests are poison for threaded email</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><strong>Formatting replies</strong> - tidy stuff up before sending it
|
||||||
|
<ul>
|
||||||
|
<li><em>Quote the text you are replying to</em> - make it clear who said what</li>
|
||||||
|
<li><em>Trim the text you are replying to</em> - you know it makes sense
|
||||||
|
<ul>
|
||||||
|
<li><em>Don't send back the PGP/GPG signature!</em> - doh!</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><em>Do not top post</em> - backwards reading like correspondents your and you unless</li>
|
||||||
|
<li><em>Use an email client that can do the right thing!</em> - MS Outlook anyone?</li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="threads">Threads</h2>
|
||||||
|
<p>The term <em>thread</em>, meaning a collection of messages relating to a subject, is quite old. It goes back to a time before the Internet. I certainly encountered it in the context of <a href="http://en.wikipedia.org/wiki/Usenet" title="Usenet News">Usenet News</a> before email existed. In current mail systems the term <em>conversation</em> is often used, but it still boils down to a way of ordering messages according to which one is a reply to another.</p>
|
||||||
|
<p>Many mail clients offer a threaded view of mail messages. I have used Thunderbird for many years, and now as a Debian user I use Icedove, the Debian version. Threading is enabled on a per-folder basis and to my mind Icedove does an excellent job.</p>
|
||||||
|
<p>While researching for this episode I found an add-on for Thunderbird called <em><a href="https://addons.mozilla.org/en-US/thunderbird/addon/threadvis/" title="ThreadVis">ThreadVis</a></em> which displays a graphic at the top of a message visualising the thread to which it belongs. I have included an image of what this looks like:</p>
|
||||||
|
<p><img src="hpr1740_img001.png" alt="Threaded messages" /><br />Picture: Threads in Icedove with ThreadVis</p>
|
||||||
|
<p>This is the thread from February 12th this year where Ken Fallon forwarded a message to the list from an organisation called <em>Cybrary</em>.</p>
|
||||||
|
<p>Notice how the thread is displayed in the Thunderbird pane. I have enabled threading in the folder and have expanded this thread by clicking on the triangle to the left, and the subject of each message is displayed. There are lines connecting messages, with indentation to show their level.</p>
|
||||||
|
<p>The <em>ThreadVis</em> display also does a nice job of showing the thread in my opinion, <strong>and</strong> it indicates that there was an <em>external</em> message which began the thread (the message forwarded in the first email from <em>Cybrary</em>), which it represents as a grey box. The messages are represented as coloured circles connected by lines. The lines show the reply relationship between messages and the length of the line represents the time between messages. Each of the messages can be viewed in a pop-up by hovering over the coloured circles, or can be opened by clicking the circle. The various authors in a thread are colour coded.</p>
|
||||||
|
<p>The only slight down-side I have found with ThreadVis is that the Global search and indexer option in Thunderbird has to be on. I had switched this off in the past because it made my Core 2 Duo workstation run slowly. On my current Core i7 with 16Gb RAM it seems to run just fine. ThreadVis uses this index to enable it to thread across all folders, which Thunderbird itself does not do.</p>
|
||||||
|
<h2 id="how-email-threads-work">How Email Threads Work</h2>
|
||||||
|
<p>To look into email threads we need to examine the way email works in more detail.</p>
|
||||||
|
<p>The structure of an email message is defined by an Internet specification document known as an <a href="http://en.wikipedia.org/wiki/Request_for_Comments" title="RFC">RFC</a> (Request For Comments). The particular one covering email is <a href="http://www.rfc-editor.org/rfc/rfc5322.txt" title="RFC5322">RFC 5322</a> "<em>Internet Message Format</em>".</p>
|
||||||
|
<p>An email message consists of two parts, the <em>header</em> and the <em>body</em>. To be precise, when the message is in transit it is enclosed in a structure called the <em>envelope</em>, but that is removed upon delivery. We will not go into a lot of detail on the structure of email messages here. There are many sources of this information, such as the Wikipedia article on <a href="http://en.wikipedia.org/wiki/Email" title="Email">Email</a> linked to at the end.</p>
|
||||||
|
<p>The message header contains lines known as <em>fields</em> in the format:</p>
|
||||||
|
<pre><code>Name: Value</code></pre>
|
||||||
|
<p>The body part contains the actual message content and can vary in structure from simple text to an arbitrarily complex hierarchy of <a href="http://en.wikipedia.org/wiki/MIME" title="MIME">MIME</a> objects such as HTML, pictures, videos and so on. We will not look at the structure of this part of the message any more here.</p>
|
||||||
|
<p>Some examples of the header fields in a message are:</p>
|
||||||
|
<pre><code>Date: Thu, 12 Feb 2015 15:08:12 +0100
|
||||||
|
From: Ken Fallon <ken@fallon.ie>
|
||||||
|
To: HPR Hacker Public Radio Mailing List <hpr@hackerpublicradio.org>
|
||||||
|
Subject: [Hpr] Fwd: Cross-Promotional Opportunties</code></pre>
|
||||||
|
<p>These are frequently used by the mail client when displaying the message, as can be seen in the picture above.</p>
|
||||||
|
<h3 id="the-message-id-header-field">The "Message-ID:" Header Field</h3>
|
||||||
|
<p>Mail messages also contain a header field which contains an unique identifier for the particular message. This field is named <code>Message-ID:</code>, and contains a value which looks a little like an email address but is not, for example:</p>
|
||||||
|
<pre><code>Message-ID: <54DCB3CC.3090906@fallon.ie></code></pre>
|
||||||
|
<p>This message identifier (the value part) is intended to be machine readable and is not necessarily meaningful to humans. The message identifier is intended to be a globally unique identifier for a message.</p>
|
||||||
|
<p>It is not mandatory for this field to be present according to the standards, but without it a lot of the important features of modern email systems fail. Email clients which do not generate a <code>Message-ID:</code> can be regarded as broken I think.</p>
|
||||||
|
<h3 id="the-in-reply-to-and-references-header-fields">The "In-Reply-To:" and "References:" Header Fields</h3>
|
||||||
|
<p>When an email client is used to reply to a message it generates header fields which refer back to the <em>ancestors</em> of the message. These fields are named <code>In-Reply-To:</code> and <code>References:</code>.</p>
|
||||||
|
<p>The <code>In-Reply-To:</code> field normally contains a single value which refers to the <em>parent</em> message. It does this by using the <code>Message-ID:</code> value from the parent message.</p>
|
||||||
|
<p>The <code>References:</code> field can contain much of the information required to build a thread. Sadly it cannot be relied on to contain all of the thread information. Normally it will contain the contents of the parent's <code>References:</code> field (if any) followed by the contents of the parent's <code>Message-ID:</code> field.</p>
|
||||||
|
<p>If the parent message does not contain a <code>References:</code> field but does have an <code>In-Reply-To:</code> field containing a single message identifier, then the <code>References:</code> field will contain the contents of the parent's <code>In-Reply-To:</code> field followed by the contents of the parent's <code>Message-ID:</code> field.</p>
|
||||||
|
<p>So the first reply to the above message contains the following fields:</p>
|
||||||
|
<pre><code>References: <1423749551922.21495@cybrary.it> <54DCB3CC.3090906@fallon.ie>
|
||||||
|
In-Reply-To: <54DCB3CC.3090906@fallon.ie></code></pre>
|
||||||
|
<p>As expected, the <code>In-Reply-To:</code> field contains the contents of the parent message's <code>Message-ID:</code> field. The <code>References:</code> field contains the same, but, perhaps surprisingly, it also contains the contents of the <code>Message-ID:</code> field of the message that was originally forwarded to the mailing list.</p>
|
||||||
|
<p>That is because the first message in the thread contains the following header fields:</p>
|
||||||
|
<pre><code>References: <1423749551922.21495@cybrary.it>
|
||||||
|
In-Reply-To: <1423749551922.21495@cybrary.it>
|
||||||
|
X-Forwarded-Message-Id: <1423749551922.21495@cybrary.it></code></pre>
|
||||||
|
<p>This is how <em>ThreadVis</em> was able to show that another message was referenced in the thread. It did this with a grey box to signify that the message was not present, as we saw.</p>
|
||||||
|
<h3 id="so-what-is-a-thread-then">So, what is a thread then?</h3>
|
||||||
|
<p>You have probably realised from the description so far, an email message thread is defined by these links. Each message points to its ancestors, and if the whole collection of such messages is analysed it is possible to build details of the <em>children</em> of each message as well.</p>
|
||||||
|
<p>While researching this topic I came across <a href="http://www.jwz.org/doc/threading.html" title="Jamie Zawinski">Jamie Zawinski</a>'s description of his algorithm for analysing a thread as used in early Mozilla products. I found this fascinating since I'd worked out my own algorithm when trying to analyse the messages from the HPR mailing list on Gmane.</p>
|
||||||
|
<p><em>However, I appreciate that you might be less enthusiastic about this and will leave it here!</em></p>
|
||||||
|
<h2 id="list-etiquette">List Etiquette</h2>
|
||||||
|
<p>Leaving the ultra technical stuff behind, let's look at the etiquette subject itself (also referred to as <em>netiquette</em>). There are a several behaviours which constitute good list etiquette. Maintaining thread consistency is one, and the other concerns the citation of the previous message.</p>
|
||||||
|
<h3 id="threads-1">Threads</h3>
|
||||||
|
<h4 id="use-reply-in-your-mail-client">Use "Reply" in your mail client</h4>
|
||||||
|
<p>As we have seen, all you need to do to ensure that your reply on a mailing list is properly threaded is to use your mail client's <em>Reply</em> facility. This will perform the steps necessary to insert the correct headers and all will go along fine.</p>
|
||||||
|
<p>If your mail client can't do this then I'd be fascinated to hear about it. I'd guess you either need to configure things properly or discard it in favour of a properly standards-compliant client.</p>
|
||||||
|
<p>As an aside: you should pay attention to where your reply is going. In the case of the HPR list it is not configured to direct replies to the list. In this situation most mail clients will reply by default to the sender, and this will usually not include the list itself.</p>
|
||||||
|
<p>My email client has a <em>Reply to List</em> function, but that does not send a copy to the sender. It also has a <em>Reply to All</em> option which replies to the list and the sender. I usually use the former since I can usually assume that the sender will get the reply through the list.</p>
|
||||||
|
<h4 id="do-not-change-the-subject-line">Do not change the "Subject" line</h4>
|
||||||
|
<p>It's usually seen as bad etiquette to change the <code>Subject:</code> field in a thread. Sometimes people will correct a misleading subject, but for clarity this should be done as follows:</p>
|
||||||
|
<pre><code>Subject: Aardvarks
|
||||||
|
Subject: The price of beef [was "Re: Aardvarks"]</code></pre>
|
||||||
|
<p>Keeping a reference back to the original subject makes it clear that the change was well considered and (probably) appropriate.</p>
|
||||||
|
<h4 id="do-not-try-to-start-a-new-thread-by-replying-to-an-old-one">Do not try to start a new thread by replying to an old one</h4>
|
||||||
|
<p>Sometimes you will see users of a mailing list trying to start a brand-new thread by replying to a message in an old one, and using a new subject. This is bad etiquette and can also be counter-productive in some circumstances.</p>
|
||||||
|
<p>For example, the script that summarises message threads in the HPR Community News show notes will not see such a message as a new thread and will not list it in the summary. See the example in the image above, where an existing thread is used to try and start a new topic with the subject "Intro and Outro". Notice how the summary in the notes for the <a href="http://hackerpublicradio.org/eps.php?id=1716">HPR Community News for February 2015</a> does not include the topic "Intro and Outro" for this reason.</p>
|
||||||
|
<h4 id="do-not-start-a-new-thread-to-reply-to-an-existing-one">Do not start a new thread to reply to an existing one</h4>
|
||||||
|
<p>Another common mistake when intending to join a conversation is to create a brand new message and copy the subject of the relevant thread. As we have seen, this will result in the message not being joined to the thread because the mail client will not be able to generate the necessary headers.</p>
|
||||||
|
<p>However, some thread analysing systems try very hard to get around this problem. The strategy is to look for "<em>orphaned</em>" messages like this with a subject matching an existing thread, then join them into this thread at a position based on the time stamp. The Gmane system does this, as does <a href="http://www.jwz.org/doc/threading.html" title="Jamie Zawinski">Jamie Zawinski</a>'s system (according to his description). My HPR summary script also does this. However, Thunderbird does not do this when displaying threads.</p>
|
||||||
|
<p>Of course, no algorithm is able to perform a repair like this if the subject line has been altered, so please do not rely on it.</p>
|
||||||
|
<h4 id="do-not-reply-to-digest-messages">Do not reply to digest messages</h4>
|
||||||
|
<p>Many mailing list systems provide a digest facility, where all messages in a period such as a day, or a certain number of messages, are bundled up and sent out together, rather than messages being sent individually. This can be a great convenience if the list is very busy or contains newsletters or other "read only" material.</p>
|
||||||
|
<p>Many mailing list systems, including "<em>Mailman</em>" used for the HPR list, are able to generate plain text or MIME digests. The plain text format conforms to <a href="http://www.faqs.org/rfcs/rfc1153.html" title="RFC1153">RFC1153</a> which is a good format for human readability, but removes all headers from each message, including those required for threading. The MIME format sends each message as a MIME attachment to a digest message. This format sends the full headers, but since they are embedded in another message, not all mail clients can deal with them.</p>
|
||||||
|
<p>If the list subscribers use it for discussions, receiving digests can be a problem if you ever want to reply to anything. Replying directly to a digest will <strong>not</strong> result in your reply being part of a thread. The message you are replying to will probably be one of several, and will be encapsulated in the digest message. The digest will not usually convey the identifiers of its constituent messages, and even if it does, most email clients are unable to reply to a message within a message.</p>
|
||||||
|
<p>For a low traffic list like the HPR list it would be better not to subscribe to the digest list. If you do, it would be best not to reply to these messages.</p>
|
||||||
|
<h3 id="formatting-replies">Formatting replies</h3>
|
||||||
|
<p>When replying to a message it is highly desirable to format the original message and your reply, for reasons of clarity, legibility and economy. See the <a href="http://en.wikipedia.org/wiki/Posting_style" title="Posting Style">Wikipedia article</a> on posting style for an in-depth treatment.</p>
|
||||||
|
<p>Many mail clients offer the ability to perform formatting on the original message when replying, and it is recommended that this feature be used wherever available.</p>
|
||||||
|
<h4 id="quote-the-text-you-are-replying-to">Quote the text you are replying to</h4>
|
||||||
|
<p>It is regarded as bad etiquette not to mark the original text in a reply. The method used most often is to start with a line in a format similar to:</p>
|
||||||
|
<p>On <em>date</em> <em>time</em>, <em>author</em> wrote:</p>
|
||||||
|
<p>Where <em>date</em> and <em>time</em> are the time stamp for the original message and <em>author</em> is the sender's name and/or email address.</p>
|
||||||
|
<p>The text of the original message then follows marked with the characters "<code>></code>" (a greater than sign and a space). For example the initial reply might look like this:</p>
|
||||||
|
<pre><code>On 01/01/1505 20:30, Fr. Benedictus wrote:
|
||||||
|
> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||||
|
> tempor incididunt ut labore et dolore magna aliqua.</code></pre>
|
||||||
|
<p>If a third person then replies to the reply, they should also do the same thing, keeping the original quoted reply, such as:</p>
|
||||||
|
<pre><code>On 01/01/1505 20:34, Fr. Alessandro wrote:
|
||||||
|
|
||||||
|
> On 01/01/1505, Fr. Benedictus wrote:
|
||||||
|
> > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||||
|
> > eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
|
>
|
||||||
|
> At vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||||
|
> blanditiis praesentium voluptatum deleniti atque corrupti quos dolores</code></pre>
|
||||||
|
<p>Most, if not all, mail clients will do this, or something similar, for you.</p>
|
||||||
|
<p>Also, many mail clients will make this layout much easier to read by various methods. For example, a Thunderbird add-on can colour the different levels of quotes to make them easier to follow. Another will collapse all quotes, replacing them with buttons which can be clicked to expand the collapsed quoted text.</p>
|
||||||
|
<h4 id="trim-the-text-you-are-replying-to">Trim the text you are replying to</h4>
|
||||||
|
<p>It is considered bad etiquette to leave the entirety of the original text in the reply. Some degree of trimming is most desirable, but this must be done so as to leave the meaning intact. Someone reading the thread in the future should be able to understand the conversation.</p>
|
||||||
|
<p>Salutations and signatures can and should be removed from the original text.</p>
|
||||||
|
<p>It is important to remove the sender's PGP/GPG signature if there is one. Without doing this mail clients which understand these items will become confused about what is being signed and by whom.</p>
|
||||||
|
<p>It is my experience that clients which are capable of signing and encrypting/decrypting messages will do this removal for you.</p>
|
||||||
|
<h4 id="do-not-top-post">Do not top post</h4>
|
||||||
|
<p>The term "<em>top posting</em>" refers to the practice of placing the reply before the text of the previous message. This is generally regarded as bad etiquette since it reverses the normal flow of conversation and requires the message to be read from the bottom up. In the case where several people have replied to a message, some top posting and others replying beneath, the end result can be almost indecipherable.</p>
|
||||||
|
<p>Most mail clients will offer the facility of positioning your text <strong>after</strong> the original text, and this feature should be enabled.</p>
|
||||||
|
<p>Some people feel that a top posted reply is more convenient in that they don't have to scroll past all the preceding material to read it. However, using an email client which can collapse and expand quotes is a good compromise here. If all but the last reply is collapsed this shrinks the message down considerably, yet the intermediate text can be consulted if necessary.</p>
|
||||||
|
<p>The screenshot below shows a reply to a message where the previous quoted text has been collapsed. Ironically the hidden message started with a top post!</p>
|
||||||
|
<p><img src="hpr1740_img002.png" alt="Message with collapsed quote" /><br />Picture: Message with collapsed quote</p>
|
||||||
|
<p>To be fair, the subject of top posting seems to be controversial and possibly in a state of flux. While preparing this show I found a lengthy discussion of the right way to reply to a mailing list on the <em>Mailman-Users</em> mailing list. You can read it <a href="http://www.mail-archive.com/mailman-users%40python.org/msg66089.html" title="Mailman-Users">here</a>. There are some interesting points made in this thread, including the fact that the authors of many modern mail clients are now forcing users away from the more normal posting style. I certainly experienced this in my working life when the installation of Microsoft mail products in the organisation changed posting behaviour for the worse.</p>
|
||||||
|
<h4 id="use-an-email-client-that-can-do-the-right-thing">Use an email client that can do the right thing!</h4>
|
||||||
|
<p>As you might have noticed if you have read the <a href="http://en.wikipedia.org/wiki/Posting_style" title="Posting Style">Wikipedia article</a> on posting style below, some mail clients are not capable of following these guidelines. <em>Microsoft Outlook</em> seems particularly challenged in this area, so if you can, avoid it and other clients like it!</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ol type="1">
|
||||||
|
<li>Gmane archive of the Hacker Public Radio mailing list: <a href="http://dir.gmane.org/gmane.network.syndication.podcast.hacker-public-radio">http://dir.gmane.org/gmane.network.syndication.podcast.hacker-public-radio</a></li>
|
||||||
|
<li>Wikipedia article on message groupings referred to as <em>conversations</em>, <em>topic threads</em>, or <em>threads</em>: <a href="http://en.wikipedia.org/wiki/Conversation_threading">http://en.wikipedia.org/wiki/Conversation_threading</a></li>
|
||||||
|
<li>A brief note on how to punctuate the phrase "<em>do's and don'ts</em>": <a href="http://www.visualthesaurus.com/cm/wc/dos-and-donts-or-dos-and-donts/">http://www.visualthesaurus.com/cm/wc/dos-and-donts-or-dos-and-donts/</a></li>
|
||||||
|
<li>Wikipedia article on <em>Usenet</em>: <a href="http://en.wikipedia.org/wiki/Usenet">http://en.wikipedia.org/wiki/Usenet</a></li>
|
||||||
|
<li>Thunderbird add-on <em>ThreadVis</em>: <a href="https://addons.mozilla.org/en-US/thunderbird/addon/threadvis/">https://addons.mozilla.org/en-US/thunderbird/addon/threadvis/</a></li>
|
||||||
|
<li>Wikipedia article on the RFC document: <a href="http://en.wikipedia.org/wiki/Request_for_Comments">http://en.wikipedia.org/wiki/Request_for_Comments</a></li>
|
||||||
|
<li>Text of RFC5322: <a href="http://www.rfc-editor.org/rfc/rfc5322.txt">http://www.rfc-editor.org/rfc/rfc5322.txt</a></li>
|
||||||
|
<li>Wikipedia article on Email: <a href="http://en.wikipedia.org/wiki/Email">http://en.wikipedia.org/wiki/Email</a></li>
|
||||||
|
<li>Wikipedia article on MIME used in email: <a href="http://en.wikipedia.org/wiki/MIME">http://en.wikipedia.org/wiki/MIME</a></li>
|
||||||
|
<li>Description of a threading algorithm from Jamie Zawinski: <a href="http://www.jwz.org/doc/threading.html">http://www.jwz.org/doc/threading.html</a></li>
|
||||||
|
<li>Text of RFC1153: <a href="http://www.faqs.org/rfcs/rfc1153.html">http://www.faqs.org/rfcs/rfc1153.html</a></li>
|
||||||
|
<li>Wikipedia article on posting style: <a href="http://en.wikipedia.org/wiki/Posting_style">http://en.wikipedia.org/wiki/Posting_style</a></li>
|
||||||
|
<li>A recent <strong>large</strong> thread on the <em>Mailman-Users</em> mailing list discussing the subject of replying to lists: <a href="http://www.mail-archive.com/mailman-users%40python.org/msg66089.html">http://www.mail-archive.com/mailman-users%40python.org/msg66089.html</a></li>
|
||||||
|
</ol>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1740/hpr1740_img001.png
Executable file
|
After Width: | Height: | Size: 222 KiB |
BIN
eps/hpr1740/hpr1740_img002.png
Executable file
|
After Width: | Height: | Size: 93 KiB |
BIN
eps/hpr1757/hpr1757_full_shownotes.epub
Executable file
218
eps/hpr1757/hpr1757_full_shownotes.html
Executable file
@@ -0,0 +1,218 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Useful Bash functions (HPR Show 1757)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
<style type="text/css">
|
||||||
|
pre { background: #eee;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
overflow: auto;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Useful Bash functions (HPR Show 1757)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#overview">Overview</a></li>
|
||||||
|
<li><a href="#example-functions">Example Functions</a><ul>
|
||||||
|
<li><a href="#the-pad-function">The pad function</a><ul>
|
||||||
|
<li><a href="#code">Code</a></li>
|
||||||
|
<li><a href="#explanation">Explanation</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#the-yes_no-function">The yes_no function</a><ul>
|
||||||
|
<li><a href="#code-1">Code</a></li>
|
||||||
|
<li><a href="#explanation-1">Explanation</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
<p>I enjoy writing Bash scripts to solve various problems. In particular I have a number of scripts I use to manage the process of preparing a show for HPR, which I am developing at the moment.</p>
|
||||||
|
<p>My more complex Bash scripts use a lot of functions to perform the various tasks, and, in the nature of things, some of these functions can be of use in other scripts and are shared between them.</p>
|
||||||
|
<p>I thought I would share some of these functions with HPR listeners in the hopes that they might be useful. It would also be interesting to receive feedback on these functions and would be great if other Bash users contributed ideas of their own.</p>
|
||||||
|
<h2 id="example-functions">Example Functions</h2>
|
||||||
|
<p>The following functions are designed to be used in shell scripts. I have a few other functions, some of which I use from the command line, but I will leave discussing them for another time.</p>
|
||||||
|
<p>The way I usually include functions in scripts is to keep them all in a file I call <code>function_lib.sh</code> in the project directory. I then add the following to my script (the variable <code>BASEDIR</code> holds the project directory):</p>
|
||||||
|
<pre><code>#
|
||||||
|
# Load library functions
|
||||||
|
#
|
||||||
|
LIB="$BASEDIR/function_lib.sh"
|
||||||
|
[ -e $LIB ] || { echo "Unable to load functions"; exit; }
|
||||||
|
source $LIB</code></pre>
|
||||||
|
<h3 id="the-pad-function">The pad function</h3>
|
||||||
|
<p>This is a simple function whose purpose is to write formatted lines to the screen. It outputs some text, padded to a chosen length using a chosen character. It adds the padding on the right, left or both sides to make the text centred in the width.</p>
|
||||||
|
<p>The arguments it requires are:</p>
|
||||||
|
<ol type="1">
|
||||||
|
<li>The text to display</li>
|
||||||
|
<li>The desired length of the padded string (default 80)</li>
|
||||||
|
<li>The character to pad with (default '-')</li>
|
||||||
|
<li>The side the padding is to be added: <em>L</em>, <em>R</em> or <em>C</em> (centre) (default <em>R</em>)</li>
|
||||||
|
</ol>
|
||||||
|
<p>The function might be called as follows to achieve the results shown:</p>
|
||||||
|
<pre><code>pad 'Title ' 40 '='
|
||||||
|
Title ==================================
|
||||||
|
|
||||||
|
pad ' Title' 40 '=' L
|
||||||
|
================================== Title
|
||||||
|
|
||||||
|
pad ' Title ' 40 '=' C
|
||||||
|
================ Title =================</code></pre>
|
||||||
|
<p>It can also be used to output a line of 80 hyphens with the call:</p>
|
||||||
|
<pre><code>pad '-'</code></pre>
|
||||||
|
<p>I often use this function to generate lines and headers in reports I display on the terminal.</p>
|
||||||
|
<h4 id="code">Code</h4>
|
||||||
|
<pre><code> 1 #=== FUNCTION ================================================================
|
||||||
|
2 # NAME: pad
|
||||||
|
3 # DESCRIPTION: Pad $text on the $side with $char characters to length $length
|
||||||
|
4 # PARAMETERS: 1 - the text string to pad (no default)
|
||||||
|
5 # 2 - how long the padded string is to be (default 80)
|
||||||
|
6 # 3 - the character to pad with (default '-')
|
||||||
|
7 # 4 - the side to pad on, L or R or C for centre (default R)
|
||||||
|
8 # RETURNS: Nothing
|
||||||
|
9 #===============================================================================
|
||||||
|
10 pad () {
|
||||||
|
11 local text=${1?Usage: pad text [length] [character] [L|R|C]}
|
||||||
|
12 local length=${2:-80}
|
||||||
|
13 local char=${3:--}
|
||||||
|
14 local side=${4:-R}
|
||||||
|
15 local line l2
|
||||||
|
16
|
||||||
|
17 [ ${#text} -ge $length ] && { echo "$text"; return; }
|
||||||
|
18
|
||||||
|
19 char=${char:0:1}
|
||||||
|
20 side=${side^^}
|
||||||
|
21
|
||||||
|
22 printf -v line "%*s" $(($length - ${#text})) ' '
|
||||||
|
23 line=${line// /$char}
|
||||||
|
24
|
||||||
|
25 if [[ $side == "R" ]]; then
|
||||||
|
26 echo "${text}${line}"
|
||||||
|
27 elif [[ $side == "L" ]]; then
|
||||||
|
28 echo "${line}${text}"
|
||||||
|
29 elif [[ $side == "C" ]]; then
|
||||||
|
30 l2=$((${#line}/2))
|
||||||
|
31 echo "${line:0:$l2}${text}${line:$l2}"
|
||||||
|
32 fi
|
||||||
|
33 }</code></pre>
|
||||||
|
<h4 id="explanation">Explanation</h4>
|
||||||
|
<ul>
|
||||||
|
<li><p>I use a Vim plugin called <em>Bash Support</em> which can generate a standard comment template and function boilerplate, and I have used this here to generate this function and the comment template in lines 1-9.</p></li>
|
||||||
|
<li><p>The function starts (lines 11-14) by declaring a number of <code>local</code> variables to hold the arguments. Only one argument, the text to output, is mandatory. The declaration of variable <code>text</code> uses the Bash parameter manipulation feature <em>Display Error if Null or Unset</em> which will abort the function and the calling script with an error message if no value is provided.</p></li>
|
||||||
|
<li><p>The other variable declarations supply default values using the Bash feature <em>Use default values</em>.</p></li>
|
||||||
|
<li><p>One of the instances where the function needs to take special action is if the supplied text is as long or longer than the total length. The expression on line 17 tests for this, and if found to be true it simply displays the text and returns from the function.</p></li>
|
||||||
|
<li><p>Next (lines 19 and 20) the variable <code>char</code> is processed, ensuring it's only one character long, and <code>side</code> is forced to upper-case.</p></li>
|
||||||
|
<li><p>The next part (line 22) uses <code>printf</code> to create the padding characters. The <code>-v</code> option to <code>printf</code> writes the result to a variable. The format string just consists of a <code>%s</code> specifier, used for writing a string. The asterisk (<code>*</code>) after the percent sign (<code>%</code>) causes <code>printf</code> to get the width of the string from the argument list.</p></li>
|
||||||
|
<li><p>The first argument to <code>printf</code> (after the format string) is the result of an arithmetic expression where the length of the text is subtracted from the desired length. The second argument is a space. So, the <code>printf</code> generates a space-filled string of the required length and stores it in the variable <code>line</code>.</p></li>
|
||||||
|
<li><p>The next statement (line 23) replaces all the spaces with the padding character. This uses the Bash feature <em>Pattern substitution</em>.</p></li>
|
||||||
|
<li><p>Finally, the function uses an <code>if</code> statement (lines 25-32) to determine how to display the text and the padding. If <code>side</code> is <em>R</em> or <em>L</em> the padding is on the right or left respectively. If it is <em>C</em> then half of the padding is placed on one side and half on the other. Parts of the padding string are selected with the Bash feature <em>Substring Expansion</em> (line 31).</p></li>
|
||||||
|
</ul>
|
||||||
|
<p>The function does a good enough job for my needs. It does not deal with the case where the padding character is a space, but that is not a problem as far as I am concerned. It may be a little too simplistic for your tastes.</p>
|
||||||
|
<h3 id="the-yes_no-function">The yes_no function</h3>
|
||||||
|
<p>This another simple function which asks a question and waits for a yes/no reply. It returns a true/false result so it can be used thus:</p>
|
||||||
|
<pre><code>if ! yes_no 'Do you want to continue? ' 'No'; then
|
||||||
|
return
|
||||||
|
fi</code></pre>
|
||||||
|
<p>It takes two arguments:</p>
|
||||||
|
<ol type="1">
|
||||||
|
<li>The prompt string</li>
|
||||||
|
<li>An optional default value.</li>
|
||||||
|
</ol>
|
||||||
|
<p>It returns <code>true</code> (0) if the response is either <em>Y</em> or <em>Yes</em>, regardless of case, and <code>false</code> (1) otherwise.</p>
|
||||||
|
<h4 id="code-1">Code</h4>
|
||||||
|
<pre><code> 1 #=== FUNCTION ================================================================
|
||||||
|
2 # NAME: yes_no
|
||||||
|
3 # DESCRIPTION: Read a Yes or No response from STDIN and return a suitable
|
||||||
|
4 # numeric value
|
||||||
|
5 # PARAMETERS: 1 - Prompt string for the read
|
||||||
|
6 # 2 - Default value (optional)
|
||||||
|
7 # RETURNS: 0 for a response of Y or YES, 1 otherwise
|
||||||
|
8 #===============================================================================
|
||||||
|
9 yes_no () {
|
||||||
|
10 local prompt="${1:?Usage: yes_no prompt [default]}"
|
||||||
|
11 local default="${2// /}"
|
||||||
|
12 local ans res
|
||||||
|
13
|
||||||
|
14 if [[ -n $default ]]; then
|
||||||
|
15 default="-i $default"
|
||||||
|
16 fi
|
||||||
|
17
|
||||||
|
18 #
|
||||||
|
19 # Read and handle CTRL-D (EOF)
|
||||||
|
20 #
|
||||||
|
21 read -e $default -p "$prompt" ans
|
||||||
|
22 res="$?"
|
||||||
|
23 if [[ $res -ne 0 ]]; then
|
||||||
|
24 echo "Read aborted"
|
||||||
|
25 return 1
|
||||||
|
26 fi
|
||||||
|
27
|
||||||
|
28 ans=${ans^^}
|
||||||
|
29 ans=${ans//[^YESNO]/}
|
||||||
|
30 if [[ $ans =~ ^Y(E|ES)?$ ]]; then
|
||||||
|
31 return 0
|
||||||
|
32 else
|
||||||
|
33 return 1
|
||||||
|
34 fi
|
||||||
|
35 }</code></pre>
|
||||||
|
<h4 id="explanation-1">Explanation</h4>
|
||||||
|
<ul>
|
||||||
|
<li><p>The function starts (lines 10 and 11) by declaring a number of <code>local</code> variables to hold the arguments. Only one argument, the prompt, is mandatory. The declaration of variable <code>prompt</code> uses the Bash parameter manipulation feature <em>Display Error if Null or Unset</em> which will abort the function and the calling script with an error message if no value is provided.</p></li>
|
||||||
|
<li><p>The declaration of the <code>default</code> variable (line 11) copies the second argument and strips any spaces from it. This is because the answers catered for must not contain spaces.</p></li>
|
||||||
|
<li><p>If the <code>default</code> variable is not empty (lines 14-16) the string "<code>-i</code>" is prepended to it. This is going to be used as an option in the following <code>read</code> command. Note that the substitution of <code>default</code> should really be enclosed in double quotes if it contained spaces, but since we have stripped them out previously we can do this.</p></li>
|
||||||
|
<li>Next (line 21) a <code>read</code> command is issued to obtain input from the user.
|
||||||
|
<ul>
|
||||||
|
<li>The "<code>-e</code>" option ensures that the <em>readline</em> library is used to read the value. This permits line editing in the same way as on the command line.</li>
|
||||||
|
<li>If there is a default value then this is passed through the "<code>-i</code>" option which we have already added to the <code>default</code> variable. If there is no default value then nothing will be substituted here.</li>
|
||||||
|
<li>The "<code>-p</code>" option specifies the prompt string.</li>
|
||||||
|
<li>The result of the <code>read</code> is written to the variable <code>ans</code>.</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><p>The <code>read</code> command returns a true or false result (as do all Bash commands), and this can be found in the special variable "<code>?</code>". This is stored in the local variable <code>res</code> (line 22).</p></li>
|
||||||
|
<li><p>If the <code>res</code> variable is not true (0) then the following <code>if</code> statement (lines 23-26) will display "Read aborted" and exit the function with a false result. This false result from the <code>read</code> will result from the user pressing <em>CTRL-D</em> (meaning <em>end of file</em>) to abort the script.</p></li>
|
||||||
|
<li><p>The variable <code>ans</code> contains the answer the user typed (or accepted) and this is then processed in various ways (lines 28 and 29). First it is forced to upper case, then any letters other than "<em>YESNO</em>" are removed.</p></li>
|
||||||
|
<li><p>Finally, an <code>if</code> statement (lines 30-34) compares <code>ans</code> to the regular expression <code>^Y(E|ES)?$</code>. This matches if the answer begins with a Y and is optionally followed by an E or by ES. If there is a match the function returns true (0), otherwise it returns false (1).</p></li>
|
||||||
|
</ul>
|
||||||
|
<p>This way of doing things means that the reply '<code>Yup great</code>' is stripped down to <code>YE</code> which is a match. Many other words that reduce to Y, YE or YES like '<code>Yeast</code>' also match. This might not be a good idea in your particular case.</p>
|
||||||
|
<p>The other aspect of this function you might find slightly undesirable is the way the default is provided. If given, the default value will be on the input line and to override it you will need to delete it (<em>CTRL-W</em> is what I use). I am happy with this but you might not be!</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ol type="1">
|
||||||
|
<li><em>Bash Support</em> Vim plugin: <a href="http://www.vim.org/scripts/script.php?script_id=365">http://www.vim.org/scripts/script.php?script_id=365</a></li>
|
||||||
|
<li>HPR episode <em>Bash parameter manipulation</em>: <a href="http://hackerpublicradio.org/eps/hpr1648">http://hackerpublicradio.org/eps/hpr1648</a></li>
|
||||||
|
<li>How to write functions (from <em>The Linux Documentation Project</em>):
|
||||||
|
<ul>
|
||||||
|
<li><em>Functions</em>: <a href="http://tldp.org/LDP/abs/html/functions.html">http://tldp.org/LDP/abs/html/functions.html</a></li>
|
||||||
|
<li><em>Complex Functions and Function Complexities</em>: <a href="http://tldp.org/LDP/abs/html/complexfunct.html">http://tldp.org/LDP/abs/html/complexfunct.html</a></li>
|
||||||
|
<li><em>Examples of functions in scripts</em>: <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_11_02.html">http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_11_02.html</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Download the <em>pad</em> and <em>yes_no</em> functions: <a href="hpr1757_functions.sh">hpr1757_functions.sh</a></li>
|
||||||
|
</ol>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
eps/hpr1757/hpr1757_functions.sh
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
#=== FUNCTION ================================================================
|
||||||
|
# NAME: pad
|
||||||
|
# DESCRIPTION: Pad $text on the $side with $char characters to length $length
|
||||||
|
# PARAMETERS: 1 - the text string to pad (no default)
|
||||||
|
# 2 - how long the padded string is to be (default 80)
|
||||||
|
# 3 - the character to pad with (default '-')
|
||||||
|
# 4 - the side to pad on, L or R or C for centre (default R)
|
||||||
|
# RETURNS: Nothing
|
||||||
|
#===============================================================================
|
||||||
|
pad () {
|
||||||
|
local text=${1?Usage: pad text [length] [character] [L|R|C]}
|
||||||
|
local length=${2:-80}
|
||||||
|
local char=${3:--}
|
||||||
|
local side=${4:-R}
|
||||||
|
local line l2
|
||||||
|
|
||||||
|
[ ${#text} -ge $length ] && { echo "$text"; return; }
|
||||||
|
|
||||||
|
char=${char:0:1}
|
||||||
|
side=${side^^}
|
||||||
|
|
||||||
|
printf -v line "%*s" $(($length - ${#text})) ' '
|
||||||
|
line=${line// /$char}
|
||||||
|
|
||||||
|
if [[ $side == "R" ]]; then
|
||||||
|
echo "${text}${line}"
|
||||||
|
elif [[ $side == "L" ]]; then
|
||||||
|
echo "${line}${text}"
|
||||||
|
elif [[ $side == "C" ]]; then
|
||||||
|
l2=$((${#line}/2))
|
||||||
|
echo "${line:0:$l2}${text}${line:$l2}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#=== FUNCTION ================================================================
|
||||||
|
# NAME: yes_no
|
||||||
|
# DESCRIPTION: Read a Yes or No response from STDIN and return a suitable
|
||||||
|
# numeric value
|
||||||
|
# PARAMETERS: 1 - Prompt string for the read
|
||||||
|
# 2 - Default value (optional)
|
||||||
|
# RETURNS: 0 for a response of Y or YES, 1 otherwise
|
||||||
|
#===============================================================================
|
||||||
|
yes_no () {
|
||||||
|
local prompt="${1:?Usage: yes_no prompt [default]}"
|
||||||
|
local default="${2// /}"
|
||||||
|
local ans res
|
||||||
|
|
||||||
|
if [[ -n $default ]]; then
|
||||||
|
default="-i $default"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read and handle CTRL-D (EOF)
|
||||||
|
#
|
||||||
|
read -e $default -p "$prompt" ans
|
||||||
|
res="$?"
|
||||||
|
if [[ $res -ne 0 ]]; then
|
||||||
|
echo "Read aborted"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ans=${ans^^}
|
||||||
|
ans=${ans//[^YESNO]/}
|
||||||
|
if [[ $ans =~ ^Y(E|ES)?$ ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
BIN
eps/hpr1776/hpr1776_full_shownotes.epub
Executable file
359
eps/hpr1776/hpr1776_full_shownotes.html
Executable file
@@ -0,0 +1,359 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Vim Hints 004 (HPR Show 1776)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Vim Hints 004 (HPR Show 1776)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#more-movement-commands">More movement commands</a><ul>
|
||||||
|
<li><a href="#sentences-and-paragraphs">Sentences and paragraphs</a></li>
|
||||||
|
<li><a href="#moving-up-and-down">Moving up and down</a></li>
|
||||||
|
<li><a href="#searching">Searching</a></li>
|
||||||
|
<li><a href="#matching-pairs">Matching pairs</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#commands-that-make-changes">Commands that make changes</a><ul>
|
||||||
|
<li><a href="#insert-commands">Insert commands</a><ul>
|
||||||
|
<li><a href="#appending-text">Appending text</a></li>
|
||||||
|
<li><a href="#inserting-text">Inserting text</a></li>
|
||||||
|
<li><a href="#beginning-a-new-line">Beginning a new line</a></li>
|
||||||
|
<li><a href="#examples-of-text-insertion">Examples of text insertion</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#deletion-commands">Deletion commands</a></li>
|
||||||
|
<li><a href="#change-commands">Change commands</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#changes-and-movement">Changes and movement</a><ul>
|
||||||
|
<li><a href="#deleting-with-movement">Deleting with movement</a></li>
|
||||||
|
<li><a href="#changing-with-movement">Changing with movement</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#configuration-file">Configuration file</a><ul>
|
||||||
|
<li><a href="#the-story-so-far">The story so far</a></li>
|
||||||
|
<li><a href="#stop-beeping">Stop beeping!</a></li>
|
||||||
|
<li><a href="#showing-incomplete-commands">Showing incomplete commands</a></li>
|
||||||
|
<li><a href="#command-history">Command history</a></li>
|
||||||
|
<li><a href="#ignore-case-when-searching">Ignore case when searching</a></li>
|
||||||
|
<li><a href="#searching-incrementally">Searching incrementally</a></li>
|
||||||
|
<li><a href="#wrapping-the-search-around">Wrapping the search around</a></li>
|
||||||
|
<li><a href="#highlighting-the-search">Highlighting the search</a></li>
|
||||||
|
<li><a href="#enable-extra-features-in-insert-mode">Enable extra features in INSERT mode</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#summary">Summary</a><ul>
|
||||||
|
<li><a href="#configuration-file-1">Configuration file</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="more-movement-commands">More movement commands</h2>
|
||||||
|
<p>So far we have seen how to move by character, by word and by line. We saw how Vim has two concepts of <em><code>word</code></em> with some demonstrations of what that means.</p>
|
||||||
|
<p>Now we will look at more movement commands.</p>
|
||||||
|
<p>This information can be found in the Vim Help (type <strong>:help motion.txt</strong>) and is online <a href="http://vimdoc.sourceforge.net/htmldoc/motion.html" title="Motion">here</a>. I will be making reference to the Vim documentation in this episode, even though it is very detailed and covers many aspects which we have not yet covered. There is a help page about Vim Help (type <strong>:h help</strong>) which is online <a href="http://vimdoc.sourceforge.net/htmldoc/helphelp.html" title="Vim Help">here</a>.</p>
|
||||||
|
<h3 id="sentences-and-paragraphs">Sentences and paragraphs</h3>
|
||||||
|
<p>Sentences and paragraphs are referred to as <em>text objects</em> in Vim.</p>
|
||||||
|
<p>A sentence is defined as ending at a '.', '!' or '?' followed by either the end of a line, or by a space or tab.</p>
|
||||||
|
<p>In <em>Normal</em> mode the <strong>)</strong> command moves forward one sentence, and <strong>(</strong> moves backward. Both commands, like those we have seen before, take a count, so <strong>3)</strong> moves forward three sentences.</p>
|
||||||
|
<p>In the context of Vim a paragraph is a group of sentences which begins after each empty line.</p>
|
||||||
|
<p>In <em>Normal</em> mode the <strong>}</strong> command moves forward one paragraph, and <strong>{</strong> moves backward. Again both commands take a count, so <strong>2}</strong> moves forward two paragraphs.</p>
|
||||||
|
<h3 id="moving-up-and-down">Moving up and down</h3>
|
||||||
|
<p>There are many ways of moving up and down in a file. We have already seen two such commands: <strong>-</strong> and <strong>+</strong>. Both can be preceded by a number, so <strong>10-</strong> moves up ten lines and positions to the first non-blank character and <strong>10+</strong> moves downwards in an equivalent way.</p>
|
||||||
|
<p>The <strong>G</strong> command will move to a specific line in the file. Typing a <strong>G</strong> on its own in <em>Normal</em> mode will move to the end of the file. Typing <strong>1G</strong> will move to the first line of the file (there is also a <strong>gg</strong> command that does the same). Otherwise, any number before the <strong>G</strong> will move to that line, so <strong>42G</strong> moves to line 42.</p>
|
||||||
|
<p>The <strong>gg</strong> command mentioned above can also be used to move to a particular line, so <strong>42gg</strong> moves to line 42 in the same way as <strong>42G</strong>.</p>
|
||||||
|
<h3 id="searching">Searching</h3>
|
||||||
|
<p>Not surprisingly, with Vim you can search the file you are editing. Full information can be found in the Vim Help (type <strong>:h pattern.txt</strong>) or online <a href="http://vimdoc.sourceforge.net/htmldoc/pattern.html" title="Searching">here</a>.</p>
|
||||||
|
<p>Searching forward is initiated by typing the <strong>/</strong> key in <em>Normal</em> mode, and to search backward the <strong>?</strong> key is used.</p>
|
||||||
|
<p>When a search is initiated the <strong>/</strong> or <strong>?</strong> character appears in the command line at the bottom of the screen and the next characters you type are the search target. The typing of the target is ended by pressing the <strong><CR></strong> (or <strong><Enter></strong>) key and the search is initiated.</p>
|
||||||
|
<p>The search target can be something quite simple like a sequence of letters and numbers, but it is actually a <em>pattern</em> which can be a <em>regular expression</em>. This is quite a large subject so we will deal with it in more depth in a later episode of this series. For now we will restrict ourselves to the simpler aspects.</p>
|
||||||
|
<p>Typing the following sequence in <em>Normal</em> mode:</p>
|
||||||
|
<pre><code>/the<CR></code></pre>
|
||||||
|
<p>will result in a search for the characters <strong>the</strong> and the cursor will be positioned on the next occurrence forward from the current location.</p>
|
||||||
|
<p>Pressing the <strong><Esc></strong> key while typing the search target will abort the search.</p>
|
||||||
|
<p>Once the first occurrence has been found pressing <strong>n</strong> will move to the next occurrence. This results in forward movement if the search used <strong>/</strong> and backward movement when using <strong>?</strong>.</p>
|
||||||
|
<p>Pressing <strong>N</strong> causes the search to change direction.</p>
|
||||||
|
<p>Preceding a search by a number causes it to skip to the nth instance of the target. So typing <strong>3</strong> then:</p>
|
||||||
|
<pre><code>/but<CR></code></pre>
|
||||||
|
<p>will position to the third instance of <strong>but</strong> from the current cursor position.</p>
|
||||||
|
<p>There are a number of settings that affect the searching process, and some recommended ones are listed and explained below in the <em>Configuration file</em> section. In short they do the following:</p>
|
||||||
|
<ul>
|
||||||
|
<li>ignore the case of letters when searching <strong>except</strong> when the target contains a capital letter, when an exact match is searched for</li>
|
||||||
|
<li>start the search as the target is being typed</li>
|
||||||
|
<li>continue the search at the top (or bottom) of the file when the bottom (or top) is reached</li>
|
||||||
|
<li>highlight all the search matches</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="matching-pairs">Matching pairs</h3>
|
||||||
|
<p>Vim can move the cursor between matching pairs of characters such as '(' and ')', '{' and '}' and '[' and ']'. The command that does this in <em>Normal</em> mode is <strong>%</strong>.</p>
|
||||||
|
<p>If the cursor is placed on the opening character of the pair it will jump to the closing one. If on the closing character it will jump to the opening one. If it is positioned before the opening character of the pair it will jump to the closing one. If it is between the pair it will be positioned to the opening character.</p>
|
||||||
|
<p>The command will also move between the start and end of a C-style comment:</p>
|
||||||
|
<pre><code>/* C-style comment */</code></pre>
|
||||||
|
<p>It is possible to extend the pairs of characters that this command recognises, and there is a Vim plugin available which considerably enhances its functionality. I will leave this subject until later in this series when we look at Vim plugins.</p>
|
||||||
|
<h2 id="commands-that-make-changes">Commands that make changes</h2>
|
||||||
|
<p>When Vim is editing a file it makes a copy of its contents into a <em>buffer</em>. This is what is displayed and can be manipulated. As we already know, the changes can be discarded with <strong>:q!</strong> or saved to the file with <strong>:w</strong>. Changes can also be undone with the <strong>u</strong> command.</p>
|
||||||
|
<p>The commands in this section perform changes to the buffer.</p>
|
||||||
|
<h3 id="insert-commands">Insert commands</h3>
|
||||||
|
<p>These are commands that insert new text into the buffer. They can all be preceded by a <em>count</em>.</p>
|
||||||
|
<p>Full information can be found in the Vim Help (type <strong>:h insert.txt</strong>) or online <a href="http://vimdoc.sourceforge.net/htmldoc/insert.html" title="Insertion">here</a>.</p>
|
||||||
|
<h4 id="appending-text">Appending text</h4>
|
||||||
|
<p>The <strong>a</strong> command appends text after the cursor. Vim enters <em>Insert</em> mode and text will continue to be added until the <em>Escape</em> (<strong><Esc></strong>) key is pressed. If there was a <em>count</em> before the command the insertion is repeated that many times.</p>
|
||||||
|
<p>The <strong>A</strong> command also appends text but at the end of the line</p>
|
||||||
|
<h4 id="inserting-text">Inserting text</h4>
|
||||||
|
<p>The <strong>i</strong> command inserts text before the cursor. As before <em>Insert</em> mode is ended by pressing the <em>Escape</em> (<strong><Esc></strong>) key. If there was a <em>count</em> before the command the insertion is repeated that many times.</p>
|
||||||
|
<p>The <strong>I</strong> command inserts text at the start of the line <em>before the first non-blank</em>. The insertion is repeated if a <em>count</em> was present.</p>
|
||||||
|
<p>Vim has an alternative command <strong>gI</strong> which is like <strong>I</strong> but inserts the text in column 1.</p>
|
||||||
|
<h4 id="beginning-a-new-line">Beginning a new line</h4>
|
||||||
|
<p>The <strong>o</strong> command begins a new line below the cursor and allows text to be entered until <em>Escape</em> (<strong><Esc></strong>) is pressed. The <em>count</em> causes the new line and any text to be repeated.</p>
|
||||||
|
<p>The <strong>O</strong> command begins a new line above the cursor and allows text to be entered until <em>Escape</em> (<strong><Esc></strong>) is pressed. The <em>count</em> causes the new line and any text to be repeated.</p>
|
||||||
|
<h4 id="examples-of-text-insertion">Examples of text insertion</h4>
|
||||||
|
<ul>
|
||||||
|
<li>Typing <strong>80i-<Esc></strong> at the start of a blank line will create a line of 80 hyphens.</li>
|
||||||
|
<li>Typing <strong>eas<Esc></strong> while on a word will append an <strong>s</strong> to it</li>
|
||||||
|
<li>Typing <strong>10oHello World<Esc></strong> will cause 10 lines containing <code>Hello World</code> to be inserted</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="deletion-commands">Deletion commands</h3>
|
||||||
|
<p>Full information can be found in the Vim Help (type <strong>:h change.txt</strong>) or online <a href="http://vimdoc.sourceforge.net/htmldoc/change.html" title="Changing">here</a>.</p>
|
||||||
|
<p>The <strong>x</strong> command in <em>Normal</em> mode deletes the character under the cursor. With a <em>count</em> it deletes characters after the cursor. It will not delete beyond the end of the line.</p>
|
||||||
|
<p>The <strong>X</strong> command in <em>Normal</em> mode deletes the character before the cursor. With a <em>count</em> it will delete that number before the cursor. It will not delete before the start of the line.</p>
|
||||||
|
<p>The <strong>dd</strong> command deletes lines, one by default, or more if a <em>count</em> was given.</p>
|
||||||
|
<p>The <strong>D</strong> command deletes from the character under the cursor to the end of the line, and if a <em>count</em> was given, that number minus 1 more full lines.</p>
|
||||||
|
<p>There is a <strong>d</strong> command as well but we will look at that shortly.</p>
|
||||||
|
<h3 id="change-commands">Change commands</h3>
|
||||||
|
<p>The <strong>cc</strong> command deletes the number of lines specified by the <em>count</em> (default 1) and enters <em>Insert</em> mode to allow text to be inserted. The <strong><Esc></strong> key ends the insertion as before.</p>
|
||||||
|
<p>The <strong>C</strong> command deletes from the cursor position to the end of the line, and if a <em>count</em> was given, that number minus 1 more full lines, then enters <em>Insert</em> mode. The <strong><Esc></strong> key ends the insertion as before.</p>
|
||||||
|
<p>There is a <strong>c</strong> command as well but we will look at that shortly.</p>
|
||||||
|
<p>The <strong>s</strong> command deletes <em>count</em> characters and enters <em>Insert</em> mode, which is ended with the <strong><Esc></strong> as usual.</p>
|
||||||
|
<p>The <strong>S</strong> command is a synonym for the <strong>cc</strong> command described above.</p>
|
||||||
|
<h2 id="changes-and-movement">Changes and movement</h2>
|
||||||
|
<p>At last we can join together the movement commands and some of the commands that change things in Vim. This is where some of the real editing power of Vim resides.</p>
|
||||||
|
<h3 id="deleting-with-movement">Deleting with movement</h3>
|
||||||
|
<p>We skipped the <strong>d</strong> command in the above section because it only really comes into its own in conjunction with motions. This command when followed by a motion command deletes the thing encompassed by the motion.</p>
|
||||||
|
<p>So, for example, <strong>dw</strong> deletes to the beginning of the next word from the position of the cursor. The table below shows some examples of the operator+movement combinations:</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th style="text-align: center;">Command</th>
|
||||||
|
<th style="text-align: left;">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>dw</strong></td>
|
||||||
|
<td style="text-align: left;">Delete from the cursor to the start of the next word</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>de</strong></td>
|
||||||
|
<td style="text-align: left;">Delete from the cursor to the end of the next word</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>d$</strong></td>
|
||||||
|
<td style="text-align: left;">Delete from the cursor to the end of the line (same as <strong>D</strong>)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>d0</strong></td>
|
||||||
|
<td style="text-align: left;">Delete from before the cursor to the beginning of the line</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>d)</strong></td>
|
||||||
|
<td style="text-align: left;">Delete from the cursor to the end of the sentence</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 id="changing-with-movement">Changing with movement</h3>
|
||||||
|
<p>Similar to the <strong>d</strong> command we also skipped the <strong>c</strong> command in the above section.</p>
|
||||||
|
<p>So, for example, <strong>cw</strong> deletes to the beginning of the next word from the position of the cursor, then enters <em>Insert</em> mode for a replacement to be inserted. The table below shows some examples of the operator+movement combinations:</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th style="text-align: center;">Command</th>
|
||||||
|
<th style="text-align: left;">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>cw</strong></td>
|
||||||
|
<td style="text-align: left;">Change from the cursor to the start of the next word</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>ce</strong></td>
|
||||||
|
<td style="text-align: left;">Change from the cursor to the end of the next word</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>c$</strong></td>
|
||||||
|
<td style="text-align: left;">Change from the cursor to the end of the line (same as <strong>C</strong>)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td style="text-align: center;"><strong>c0</strong></td>
|
||||||
|
<td style="text-align: left;">Change from before the cursor to the beginning of the line</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td style="text-align: center;"><strong>c)</strong></td>
|
||||||
|
<td style="text-align: left;">Change from the cursor to the end of the sentence</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>There are many more ways of deleting and changing text with movement which we will look at in more detail in a future episode.</p>
|
||||||
|
<hr />
|
||||||
|
<h2 id="configuration-file">Configuration file</h2>
|
||||||
|
<h3 id="the-story-so-far">The story so far</h3>
|
||||||
|
<p>In the <a href="https://hackerpublicradio.org/eps/hpr1734" title="Vim Hints 003">last episode</a> we extended the configuration file with a ruler and status line. Now we can add some more settings that make Vim more convenient to use.</p>
|
||||||
|
<p>Full information on the options available in Vim can be found in the Vim Help (type <strong>:h options.txt</strong>) or online <a href="http://vimdoc.sourceforge.net/htmldoc/options.html" title="Options">here</a>.</p>
|
||||||
|
<h3 id="stop-beeping">Stop beeping!</h3>
|
||||||
|
<p>Vim has a tendency to beep to alert you to events and errors. This can be a tiny bit annoying, especially in a shared workplace. Instead of an aural alert you can request a visual one with the command:</p>
|
||||||
|
<pre><code>set visualbell</code></pre>
|
||||||
|
<p>The abbreviation is <strong>se vb</strong> and the inverse is <strong>set novisualbell</strong> or <strong>se novb</strong>.</p>
|
||||||
|
<h3 id="showing-incomplete-commands">Showing incomplete commands</h3>
|
||||||
|
<p>As we have seen, Vim commands can consist of sequences of numbers and command letters. For example <strong>23dd</strong> means delete 23 lines.</p>
|
||||||
|
<p>The command:</p>
|
||||||
|
<pre><code>set showcmd</code></pre>
|
||||||
|
<p>makes Vim show the command that is being typed. So with <strong>23dd</strong> the <strong>23d</strong> part will be visible waiting for the final <strong>d</strong>, after which the display will be cleared and the command actioned.</p>
|
||||||
|
<p>The display of the partial command is shown in the status line at the bottom of the screen.</p>
|
||||||
|
<p>The abbreviation is <strong>se sc</strong> and the effect can be reversed with <strong>set noshowcmd</strong> or <strong>se nosc</strong>.</p>
|
||||||
|
<h3 id="command-history">Command history</h3>
|
||||||
|
<p>By default Vim will remember the last 50 ':' commands (and the last 50 searches) in <em>history tables</em>. When you press the ':' key or begin a search with '/' or '?' the history table can be traversed with the up and down cursor keys. The size of all of the history tables can be extended with the command such as the following:</p>
|
||||||
|
<pre><code>set history=100</code></pre>
|
||||||
|
<p>The abbreviation for the above command is <strong>se hi=100</strong>.</p>
|
||||||
|
<h3 id="ignore-case-when-searching">Ignore case when searching</h3>
|
||||||
|
<p>Normally Vim searches for the exact case you provide in your search target. You can switch this off with the command:</p>
|
||||||
|
<pre><code>set ignorecase</code></pre>
|
||||||
|
<p>You might think that this is a little counter-intuitive; I certainly did when I first encountered it. However, in conjunction with the next command:</p>
|
||||||
|
<pre><code>set smartcase</code></pre>
|
||||||
|
<p>it seems more usable. When the <strong>smartcase</strong> option is enabled Vim will search for both lower and upper case forms when there are only lower case letters in the target, but will search for an exact match when the target is mixed case.</p>
|
||||||
|
<p>The abbreviation for <strong>set ignorecase</strong> is <strong>se ic</strong> and for <strong>set smartcase</strong> is <strong>se scs</strong>. The options can be reversed with <strong>set noignorecase</strong> (<strong>se noic</strong>) and <strong>set nosmartcase</strong> (<strong>se noscs</strong>).</p>
|
||||||
|
<h3 id="searching-incrementally">Searching incrementally</h3>
|
||||||
|
<p>While typing a search pattern, Vim can show where that part of the pattern which has been typed so far matches. This feature is enabled with the <strong>incsearch</strong> option. The matched string is highlighted, but if the pattern is invalid or not found, nothing is shown. In this mode the screen will be updated frequently so it should not be used over a slow link to a remote system!</p>
|
||||||
|
<pre><code>set incsearch</code></pre>
|
||||||
|
<p>The abbreviation is <strong>se is</strong> and the option is turned off with <strong>set noincsearch</strong> or <strong>se nois</strong>.</p>
|
||||||
|
<h3 id="wrapping-the-search-around">Wrapping the search around</h3>
|
||||||
|
<p>When searching Vim normally stops at the end (forward searches) or beginning (reverse searches) of the file. With the <strong>wrapscan</strong> option searches wrap around.</p>
|
||||||
|
<pre><code>set wrapscan</code></pre>
|
||||||
|
<p>The abbreviation is <strong>se ws</strong> and the option is turned off with <strong>set nowrapscan</strong> or <strong>se nows</strong>.</p>
|
||||||
|
<p>As the search wraps a message is displayed in the status line.</p>
|
||||||
|
<h3 id="highlighting-the-search">Highlighting the search</h3>
|
||||||
|
<p>Vim can be configured to highlight all occurrences of the search pattern with the command:</p>
|
||||||
|
<pre><code>set hlsearch</code></pre>
|
||||||
|
<p>The abbreviation is <strong>se hls</strong> and the option is turned off with <strong>set nohlsearch</strong> or <strong>se nohls</strong>.</p>
|
||||||
|
<p>The highlight stays in effect until cancelled, which can get a little tedious, so Vim allows the current pattern match to be turned off with the command <strong>:nohlsearch</strong> (abbreviated to <strong>:nohl</strong>).</p>
|
||||||
|
<h3 id="enable-extra-features-in-insert-mode">Enable extra features in INSERT mode</h3>
|
||||||
|
<p>Vim allows more functionality when in <em>Insert</em> mode than vi. It is possible to work in <em>Insert</em> mode most of the time such as in editors such as <em>Nano</em>. Enabling these features is done with the <strong>set backspace</strong> option. This is followed by a list of up to three items separated by commas:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>indent</strong> - allows backspacing over auto indents (not covered yet in this series)</li>
|
||||||
|
<li><strong>eol</strong> - allows backspacing over line breaks (thus permitting inserted lines to be joined)</li>
|
||||||
|
<li><strong>start</strong> - allows backspacing over the start of the insert to previously existing text</li>
|
||||||
|
</ul>
|
||||||
|
<p>To get the full functionality of Vim it is probably wise to use all three items:</p>
|
||||||
|
<pre><code>set backspace=indent,eol,start</code></pre>
|
||||||
|
<p>The abbreviation is <strong>se bs=indent,eol,start</strong>.</p>
|
||||||
|
<hr />
|
||||||
|
<h2 id="summary">Summary</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Movement
|
||||||
|
<ul>
|
||||||
|
<li><strong>)</strong>, <strong>(</strong> move forward and backward by sentences</li>
|
||||||
|
<li><strong>}</strong>, <strong>{</strong> move forward and backward by paragraphs</li>
|
||||||
|
<li><strong>G</strong>, <strong>gg</strong> move to a specific line or beginning or end of file</li>
|
||||||
|
<li><strong>%</strong> move between matching pairs of characters</li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Searching
|
||||||
|
<ul>
|
||||||
|
<li><strong>/</strong> to search forward</li>
|
||||||
|
<li><strong>?</strong> to search backwards</li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Changing
|
||||||
|
<ul>
|
||||||
|
<li><strong>a</strong> and <strong>A</strong> to append text</li>
|
||||||
|
<li><strong>i</strong> and <strong>I</strong> to insert text</li>
|
||||||
|
<li><strong>o</strong> and <strong>O</strong> to open a new line and insert text</li>
|
||||||
|
<li><strong>x</strong> and <strong>X</strong> to delete characters</li>
|
||||||
|
<li><strong>dd</strong> and <strong>D</strong> to delete lines</li>
|
||||||
|
<li><strong>d</strong><em>motion</em> to delete up to a movement target</li>
|
||||||
|
<li><strong>s</strong> and <strong>S</strong> to change characters</li>
|
||||||
|
<li><strong>cc</strong> and <strong>C</strong> to change lines</li>
|
||||||
|
<li><strong>c</strong><em>motion</em> to change up to a movement target</li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="configuration-file-1">Configuration file</h3>
|
||||||
|
<pre><code>" Ensure Vim runs as Vim
|
||||||
|
set nocompatible
|
||||||
|
|
||||||
|
" Keep a backup file
|
||||||
|
set backup
|
||||||
|
|
||||||
|
" Keep change history
|
||||||
|
set undodir=~/.vim/undodir
|
||||||
|
set undofile
|
||||||
|
|
||||||
|
" Show the line,column and the % of buffer
|
||||||
|
set ruler
|
||||||
|
|
||||||
|
" Always show a status line per window
|
||||||
|
set laststatus=2
|
||||||
|
|
||||||
|
" Show Insert, Replace or Visual on the last line
|
||||||
|
set showmode
|
||||||
|
|
||||||
|
" Stop beeping! (Flash the screen instead)
|
||||||
|
set visualbell
|
||||||
|
|
||||||
|
" Show incomplete commands
|
||||||
|
set showcmd
|
||||||
|
|
||||||
|
" Increase the command history
|
||||||
|
set history=100
|
||||||
|
|
||||||
|
" Turn off case in searches
|
||||||
|
set ignorecase
|
||||||
|
|
||||||
|
" Turn case-sensitive searches back on if there are capitals in the target
|
||||||
|
set smartcase
|
||||||
|
|
||||||
|
" Do incremental searching
|
||||||
|
set incsearch
|
||||||
|
|
||||||
|
" Set the search scan to wrap around the file
|
||||||
|
set wrapscan
|
||||||
|
|
||||||
|
" Highlight all matches when searching
|
||||||
|
set hlsearch
|
||||||
|
|
||||||
|
" Allow extra movement in INSERT mode
|
||||||
|
set backspace=indent,eol,start</code></pre>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ol type="1">
|
||||||
|
<li>Vim Help:
|
||||||
|
<ul>
|
||||||
|
<li>Using Help: <a href="http://vimdoc.sourceforge.net/htmldoc/helphelp.html">http://vimdoc.sourceforge.net/htmldoc/helphelp.html</a></li>
|
||||||
|
<li>Motion: <a href="http://vimdoc.sourceforge.net/htmldoc/motion.html">http://vimdoc.sourceforge.net/htmldoc/motion.html</a></li>
|
||||||
|
<li>Searching: <a href="http://vimdoc.sourceforge.net/htmldoc/pattern.html">http://vimdoc.sourceforge.net/htmldoc/pattern.html</a></li>
|
||||||
|
<li>Insertion: <a href="http://vimdoc.sourceforge.net/htmldoc/insert.html">http://vimdoc.sourceforge.net/htmldoc/insert.html</a></li>
|
||||||
|
<li>Changing: <a href="http://vimdoc.sourceforge.net/htmldoc/change.html">http://vimdoc.sourceforge.net/htmldoc/change.html</a></li>
|
||||||
|
<li>Options: <a href="http://vimdoc.sourceforge.net/htmldoc/options.html">http://vimdoc.sourceforge.net/htmldoc/options.html</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Graphical Cheat Sheet: <a href="http://www.viemu.com/a_vi_vim_graphical_cheat_sheet_tutorial.html">http://www.viemu.com/a_vi_vim_graphical_cheat_sheet_tutorial.html</a></li>
|
||||||
|
<li>Vim Hints Episode 3 <a href="https://hackerpublicradio.org/eps/hpr1734">https://hackerpublicradio.org/eps/hpr1734</a></li>
|
||||||
|
</ol>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1811/hpr1811_full_shownotes.epub
Executable file
197
eps/hpr1811/hpr1811_full_shownotes.html
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Life and Times of a Geek - part 2 (HPR Show 1811)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Life and Times of a Geek - part 2 (HPR Show 1811)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#introduction">Introduction</a></li>
|
||||||
|
<li><a href="#farewell-aberystwyth">Farewell Aberystwyth</a></li>
|
||||||
|
<li><a href="#a-year-out">A Year Out</a></li>
|
||||||
|
<li><a href="#university-of-manchester">University of Manchester</a><ul>
|
||||||
|
<li><a href="#doctor-of-philosophy-degree">Doctor of Philosophy Degree</a></li>
|
||||||
|
<li><a href="#zoology-department">Zoology Department</a></li>
|
||||||
|
<li><a href="#university-of-manchester-regional-computer-centre">University of Manchester Regional Computer Centre</a></li>
|
||||||
|
<li><a href="#programming-languages">Programming Languages</a><ul>
|
||||||
|
<li><a href="#fortran">Fortran</a></li>
|
||||||
|
<li><a href="#pascal">Pascal</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="introduction">Introduction</h2>
|
||||||
|
<p>In the last part of my story I told you of my first encounter with a mainframe computer and the Algol 60 language while I was an undergraduate student at Aberystwyth University.</p>
|
||||||
|
<p>Today I want to talk about the next stage as a postgraduate student at the University of Manchester.</p>
|
||||||
|
<h2 id="farewell-aberystwyth">Farewell Aberystwyth</h2>
|
||||||
|
<p>I had a wonderful three years in Aberystwyth. It was a beautiful location by the sea with access to all sorts of landscapes and environments; perfect for a Biology student. I could go on at great length about the forays into tidal pools in front of the main University buildings on the sea front, the Welsh woodlands, mountains, salt marshes and bogs we visited. I could tell you about the student who used to bring her pet Jackdaw and her Border Collie to lectures, or the tale of the incredibly fierce rat that my friend and I allowed to escape in the lab, which caused 30 students to jump on tables and chairs. However, I will not. Perhaps another time.</p>
|
||||||
|
<p>Suffice it to say that as we needed to specialise in the last year of study I gravitated towards the area of Animal Behaviour. I did a project on memory in goldfish, training them to perform a task, then using a drug on them to prevent the formation of long-term memory and showing that they had forgotten their task while the control group had not.</p>
|
||||||
|
<p>I obtained a reasonably good Honours degree in Zoology in the summer of 1972 and then had to consider what to do next.</p>
|
||||||
|
<p>I considered developing the programming skills I had acquired in a Biological context, and applied to a few places with this idea in mind. However, nobody wanted a newly graduated Zoologist who had done a little programming, it seemed, or maybe the fact that I didn't really know what to do next was glaringly obvious. I started looking for a possible place to take a postgraduate degree.</p>
|
||||||
|
<p>I was offered a place to study for a PhD in the Animal Behaviour group in the Zoology Department at the University of Manchester. As was normal in those days, I had been awarded a Local Education Authority grant to study for my first degree. However, I could not find funding for my PhD, so I put my studies on hold and went home to try and find a job, with the intention of funding myself for my first year, and seeing what happened after that.</p>
|
||||||
|
<h2 id="a-year-out">A Year Out</h2>
|
||||||
|
<p>Back home I found a job by the simple expedient of knocking on the door of a local plastics factory where I had worked before during vacations. I ended up as a labourer, doing shift work, earning about £0.50 per hour. Through this method I managed to accumulate enough to fund myself for my next year.</p>
|
||||||
|
<p>Being an inveterate hoarder I seem to have kept my employment contract, and happened to find it recently while tidying the house.</p>
|
||||||
|
<figure>
|
||||||
|
<img src="hpr1811_img001.png" alt="My contract with United Glass Closures and Plastics Ltd." /><figcaption>My contract with United Glass Closures and Plastics Ltd.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<p>In case you are wondering, the Grinding Department was responsible for chopping up all the waste plastic, melting it down and converting it to pellets so it could be re-used. It was fairly heavy, noisy, boring work, but it achieved the desired goal.</p>
|
||||||
|
<h2 id="university-of-manchester">University of Manchester</h2>
|
||||||
|
<p>In the autumn of 1973 I was in the city of Manchester, at the <a href="http://en.wikipedia.org/wiki/University_of_Manchester" title="University of Manchester">University of Manchester</a>, one of the largest universities in the UK. I was there to obtain a PhD (Doctor of Philosophy) degree, doing research in Animal Behaviour.</p>
|
||||||
|
<p><img src="hpr1811_img002.jpg" alt="Photo from Wikipedia" /><br />Original <em>Owens College</em> buildings from the Museum end<br />By DrPhoenix (Own work) [<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY-SA 3.0</a> or <a href="http://www.gnu.org/copyleft/fdl.html">GFDL</a>], <a href="https://commons.wikimedia.org/wiki/File%3AThe_Manchester_Museum.jpg">via Wikimedia Commons</a></p>
|
||||||
|
<h3 id="doctor-of-philosophy-degree">Doctor of Philosophy Degree</h3>
|
||||||
|
<p>My research topic was to be looking at how animals decide what to eat, where to look for food, how much effort to expend finding and eating it and so on. At the time this area was variously referred to as <em>feeding strategies</em>, <em><a href="http://en.wikipedia.org/wiki/Optimal_foraging_theory" title="Optimal foraging theory">optimal foraging</a></em> and by other names. Later in the decade and into the early 1980's this subject became what is now known as <em><a href="https://en.wikipedia.org/wiki/Behavioral_ecology" title="Behavioural Ecology">Behavioural Ecology</a></em>, one of the areas where Mathematical methods (and ideas from Economics) were used to describe and predict animal behaviour.</p>
|
||||||
|
<p>A recent programme in the BBC Radio 4 series "<em>In Our Time</em>" did a fine job of covering this subject and can be heard on the <a href="http://www.bbc.co.uk/programmes/b04tljk0" title="Behavioural Ecology">BBC website</a> if you are interested (and if the site is not blocked from outside the UK).</p>
|
||||||
|
<h3 id="zoology-department">Zoology Department</h3>
|
||||||
|
<p>I found myself a member of the Zoology Department, which was then a separate entity within the University. It was later incorporated into the School of Biological Sciences, after my time, in 1986. The Zoology Department had been established in 1870 and the rooms and laboratories had an ancient feel about them which I really liked. In 1973 the department was housed in a beautiful old building adjoining the <a href="http://en.wikipedia.org/wiki/Manchester_Museum" title="Manchester Museum">Manchester Museum</a>. Postgraduate students in the department were given keys to the building and these also gave access to the Museum which was linked to the front part of the building. The Museum contained some fascinating exhibits, including a number of live animals.</p>
|
||||||
|
<p>My PhD Supervisor had two other students who were starting at the same time that I was, both doing research in Animal Behaviour. Two of us were using the <a href="https://en.wikipedia.org/wiki/Barbary_dove" title="Barbary dove">Barbary Dove</a> (<em>Streptopelia risoria</em>) as our experimental animal and the other one was researching on the <a href="https://en.wikipedia.org/wiki/Common_marmoset" title="Common Marmoset">Common Marmoset</a> (Callithrix jaccus). Our animals were in the Animal House in the basement of an old building near the Zoology Department.</p>
|
||||||
|
<p>It was usual in those days for postgraduate students in the Department to begin their research projects by carrying out a literature review and writing it up for assessment. In my case this consisted of reading through any of the relevant journals held by the University Library, or for more up to date material, reading a publication called <em><a href="https://en.wikipedia.org/wiki/Current_Contents" title="Current Contents">Current Contents</a></em> which summarised recent publications in peer-reviewed scientific journals. If a paper looked interesting in Current Contents then it could either be obtained by requesting a photocopy through the Inter-Library Loans service or by writing to the author (whose address would be published with details of the paper) to ask for a reprint. Needless to say, this was a slow and laborious process, though the arrival of a new paper was an exciting event.</p>
|
||||||
|
<p>The purpose of doing the literature review was to become highly conversant with the subject and as up to date as possible with published research. This required the keeping of a good collection of references to papers and reprints, and the way to to this in those days was by keeping a filing system. I started by keeping a box file full of hand-written <a href="https://en.wikipedia.org/wiki/Index_card" title="index card">index cards</a> in alphabetical order. There was very little at that time for doing this in any other way.</p>
|
||||||
|
<p>My supervisor introduced us to a slightly more advanced technology in the form of <a href="https://en.wikipedia.org/wiki/Edge-notched_card" title="edge-notched card">edge-notched cards</a> at this time. These have holes punched all around the edges, which can be notched with a punching tool (we used scissors) to differentiate them from other cards. The principle is that cards relevant to a topic will all be notched in a particular position. They can be extracted from the deck by passing a needle or rod through the relevant hole and lifting out all the cards which are not relevant to a search. Searches can even be combined by using more than one needle or rod.</p>
|
||||||
|
<p>This system was a type of mechanical database, though I have to admit that the sophistication of this method was largely lost on us and we used the simpler methods we had already started with.</p>
|
||||||
|
<h3 id="university-of-manchester-regional-computer-centre">University of Manchester Regional Computer Centre</h3>
|
||||||
|
<p>Across the road from the Zoology Department was the Kilburn Building, which was fairly recently built, having been opened in 1972. This contained the Computer Science Department (later the <a href="https://en.wikipedia.org/wiki/School_of_Computer_Science,_University_of_Manchester" title="School of Computer Science">School of Computer Science</a>) and, on the ground floor, the University of Manchester Regional Computer Centre (UMRCC).</p>
|
||||||
|
<p><img src="hpr1811_img003.jpg" alt="Kilburn Building" /><br />The Kilburn Building<br />By M2Ys4U (Own work) [<a href="http://creativecommons.org/licenses/by-sa/3.0">CC BY-SA 3.0</a>], <a href="https://commons.wikimedia.org/wiki/File%3AManchester_Kilburn_Building.jpg">via Wikimedia Commons</a></p>
|
||||||
|
<p>UMRCC was one of the regional computer centres funded by Government to provide high-powered computer facilities for universities in the local region. UMRCC initially provided services for a group of universities which, as well as Manchester itself, included Salford, Liverpool, Keele and Lancaster. The University of London Computer Centre (ULCC) was one of the other such centres.</p>
|
||||||
|
<p>I don't know if there was much in the way of inter-computer networking going on at that time. I would not have had to use it myself being at the heart of things in Manchester, but I think the access to the Regional Centre was via Remote Job Entry (RJE) facilities at the satellite universities. Sadly, I cannot seem to find much information on these facilities now. I will leave further discussion of this subject until a later episode when I speak about finding myself at one of the satellite universities.</p>
|
||||||
|
<p>At the time that I was there, UMRCC had a state of the art <a href="https://en.wikipedia.org/wiki/CDC_7600" title="CDC 7600">CDC 7600</a> computer from Control Data Corporation, front-ended by an <a href="https://en.wikipedia.org/wiki/ICT_1900_series" title="ICL 1900 series">ICL 1906A</a>. The CDC 7600, designed by <a href="https://en.wikipedia.org/wiki/Seymour_Cray" title="Seymour Cray">Seymour Cray</a>, who also designed the <a href="https://en.wikipedia.org/wiki/Cray-1" title="Cray-1">Cray-1</a> later in his career, was considered to be the fastest supercomputer in the world at that point. I think it ran the SCOPE operating system, but I have not found much to support this vague memory. The 1906A ran the <a href="http://en.wikipedia.org/wiki/GEORGE_%28operating_system%29" title="GEORGE operating system">GEORGE</a> operating system, either GEORGE 3, or since this model had paging hardware, GEORGE 4 - I don't remember, and I can't find any records any more.</p>
|
||||||
|
<p><img src="hpr1811_img004.jpg" alt="CDC 7600" /><br />Picture: CDC 7600<br /><em>Attribution: "CDC 7600.jc" by Jitze Couperus - Flickr: Supercomputer - The Middle Ages.<br />Licensed under CC BY 2.0 via <a href="https://commons.wikimedia.org/wiki/File:CDC_7600.jc.jpg#/media/File:CDC_7600.jc.jpg">Wikimedia Commons</a></em></p>
|
||||||
|
<p>As a student I was able to get an account on these systems and soon started learning about them and using them. The main work-horse was the CDC 7600 with the 1906A (quite a powerful computer in its own right at that time) being mainly used as a gateway to the CDC. As before, programs mostly had to be written on coding sheets and punched cards generated, but at UMRCC the users had access to <a href="https://en.wikipedia.org/wiki/Keypunch" title="Card punch">card punches</a> for small amounts of work, making corrections and so forth.</p>
|
||||||
|
<p><img src="hpr1811_img005.jpg" alt="IBM 029 Card Punch" /><br />Picture: IBM 029 Card Punch from <a href="https://en.wikipedia.org/wiki/File:IBM_card_punch_029.JPG">Wikipedia</a></p>
|
||||||
|
<p>There were also teletypes available to us, connected to the ICL 1906A, but I didn't use these at the start, and I will talk about them later.</p>
|
||||||
|
<p>One of the things that fascinated me about the Computer Centre was the viewing gallery. Access to the ground floor of the building was through a corridor with a glass wall looking into the computer room. In it was all of the hardware, mainframes, tape drives, card readers, line printers and so on. The computer operators wore white coats and could be seen tending to the machines.</p>
|
||||||
|
<p>A pair of rather poor quality videos<sup><a href="https://www.youtube.com/watch?v=swpE9IS8rso" title="UMRCC Video 1">1</a>,<a href="https://www.youtube.com/watch?v=G-ObR4Z6reY" title="UMRCC Video 2">2</a></sup> are available on YouTube, made at some time in the 1980s when there were two CDC 7600s and the ICL 1906A was replaced by an Amdahl. See the links below. There are several views into the machine room from the viewing gallery in these videos. It looked similarly full of hardware in the early 1970s.</p>
|
||||||
|
<p>The building itself was heated by waste heat from this equipment. I was in Manchester during the Miners' Strike and the <a href="https://en.wikipedia.org/wiki/Three-Day_Week" title="Three-Day Week">Three-Day Week</a> when a lot of electrical equipment was shut off and lights turned out to save power. UMRCC kept going during this time and was heated where other places were not.</p>
|
||||||
|
<h3 id="programming-languages">Programming Languages</h3>
|
||||||
|
<p>During this period, I was writing programs in Algol 60 as before, though the compiler available to me was different from the one I had been used to. I also learned <a href="https://en.wikipedia.org/wiki/Fortran" title="Fortran">Fortran</a> at this time.</p>
|
||||||
|
<h4 id="fortran">Fortran</h4>
|
||||||
|
<p>Fortran seemed a strange language compared to Algol 60. Statements had a fixed layout, starting in column 7 up to column 72 with columns 73-80 often being used for sequence numbers, to keep the card deck in order. Hardware card sorters were available to sort mis-ordered decks. If a statement had to be continued to a second card then each continuation card needed a character in column 6. Columns 1-5 contained a numeric label used by <code>GOTO</code> statements and others, and if column 1 contained a <code>C</code> that made the card a comment card.</p>
|
||||||
|
<p>See the <a href="https://en.wikibooks.org/wiki/Fortran/Fortran_examples#Simple_Fortran_IV_program" title="Fortan IV Example">example</a> of a simple Fortran IV program on the WikiBooks site for what the Fortran of this time looked like.</p>
|
||||||
|
<p>In this example you will see <code>FORMAT</code> statements that define input and output formats. In this program all of these statements are collected at the top, though most people placed them after the <code>WRITE</code> statements they were associated with. As an example consider the following <code>FORMAT</code> with its associated <code>WRITE</code>:</p>
|
||||||
|
<pre><code> 601 FORMAT(4H A= ,I5,5H B= ,I5,5H C= ,I5,8H AREA= ,F10.2,12HSQUARE UNITS)
|
||||||
|
WRITE(6,601) A,B,C,AREA</code></pre>
|
||||||
|
<p>Here the <code>H</code> (Hollerith) format defines a sequence of characters of a predefined width, <code>I</code> (Integer) format defines an integer number and <code>F</code> (Floating point) defines a real or floating point number. It was quite laborious having to count the width of <code>H</code> formats in particular.</p>
|
||||||
|
<p>Also, the first character defined in a <code>FORMAT</code> statement had special significance. These were the days of line-printers and these needed control characters to be sent to them to control the line spacing. The first character output on a line by a Fortran program was a line-printer carriage control character. A space in this position tells the printer to advance to a new line on the output. A zero advances two lines (double space), a 1 advances to the top of a new page and + character will not advance to a new line, allowing overprinting.</p>
|
||||||
|
<p>Failure to remember the use of the first column could result in problems. For example, writing a column of numbers where the first digit was a '1' could result in the printer throwing large numbers of pages with just a single (truncated) number on each. Printing signed numbers starting in column 1 could result in them all overprinting one another as a consequence of the '+'. Neither of these mistakes were very popular with the computer operators!</p>
|
||||||
|
<p>The <code>WRITE</code> statement defines the output unit (6) which will be associated with a device like a file, card punch or line printer, and the format statement which defines the layout of the data. There were various ways in which units were associated with devices. Sometimes the association was by default and other times the job control cards surrounding the program itself defined these associations.</p>
|
||||||
|
<p>One of the great things about Fortran was that were many libraries available for numeric work or to plot results. I had dabbled with using a graph <a href="https://en.wikipedia.org/wiki/Plotter" title="Plotter">plotter</a> at Aberystwyth and I learned more about how to do this at Manchester. I also made heavy use of the <a href="https://en.wikipedia.org/wiki/NAG_Numerical_Library" title="NAG Library"><em>Numerical Algorithms Group</em> (NAG) Library</a> which contained many tools for numerical work like random number generators, statistical methods and matrix manipulation functions.</p>
|
||||||
|
<h4 id="pascal">Pascal</h4>
|
||||||
|
<p>The language <a href="https://en.wikipedia.org/wiki/Pascal_%28programming_language%29" title="Pascal">Pascal</a> had also started to become popular around this time. It had been published in 1970 and a CDC version had been developed. Due to its similarity with Algol 60 I wondered if it might be a language I could use.</p>
|
||||||
|
<p>I acquired a copy of the <a href="http://www.standardpascal.com/The_Programming_Language_Pascal_1973.pdf" title="The Programming Language Pascal">book</a> written by Jensen and Wirth, a strange thing which looked as if it had been generated on a typewriter, with handwritten insertions where there were unusual characters. Sadly I don't seem to have this any more; I must have lent it out and never had it returned or possibly it's lurking in some forgotten corner of my house.</p>
|
||||||
|
<p>Pascal was remarkable at the time because it had data type definitions, records, sets and pointers, which Algol 60 did not. I learnt how to use it and wrote some programs in it but it seemed rather abstract and not very practical compared to Fortran.</p>
|
||||||
|
<p>In its early incarnations Pascal required declarations to be made in a particular order:</p>
|
||||||
|
<pre><code>labels
|
||||||
|
constants
|
||||||
|
types
|
||||||
|
variables
|
||||||
|
functions and procedures</code></pre>
|
||||||
|
<ul>
|
||||||
|
<li><code>labels</code> are numeric and are the target of <code>goto</code> statements. Pascal users are <em>strongly</em> dissuaded from using <code>goto</code>!</li>
|
||||||
|
<li><code>constants</code> are identifiers associated with values</li>
|
||||||
|
<li><code>types</code> allow the programmer to define new data types</li>
|
||||||
|
<li><code>variables</code> are identifiers which refer to storage ares of various <code>types</code></li>
|
||||||
|
<li><code>functions</code> are subroutines that return a value</li>
|
||||||
|
<li><code>procedures</code> are subroutines that do not return a value</li>
|
||||||
|
</ul>
|
||||||
|
<p>In Pascal you can define types based on existing types. For example:</p>
|
||||||
|
<pre><code>type
|
||||||
|
byte = 0..255;</code></pre>
|
||||||
|
<p>this type is a sub-range of the standard type <code>integer</code>. This example also demonstrates sub-ranges.</p>
|
||||||
|
<p>Pascal contains set types, which was an innovation at the time it was created. For example, to declare a set capable of holding any lowercase letters:</p>
|
||||||
|
<pre><code>var
|
||||||
|
letterset : set of 'a'..'z';</code></pre>
|
||||||
|
<p>this could then be used for testing, such as:</p>
|
||||||
|
<pre><code>if 'a' in letterset then
|
||||||
|
...</code></pre>
|
||||||
|
<p>There were issues with the implementation of such features in the early days however. For example, it was not possible to define sets containing very large numbers of members since they were represented as bits in a byte, word or longword.</p>
|
||||||
|
<p>Pascal also allows the definition of complex data structures called <code>records</code>, such as:</p>
|
||||||
|
<pre><code>type
|
||||||
|
dates = record
|
||||||
|
day : 1..31;
|
||||||
|
month : 1..12;
|
||||||
|
year : 0..9999
|
||||||
|
end;
|
||||||
|
var
|
||||||
|
today : dates;</code></pre>
|
||||||
|
<p>There were also issues with these data types in the early days. It was possible in the language to define files containing these items, but many Pascal implementations could not handle them.</p>
|
||||||
|
<p>The <a href="https://en.wikipedia.org/wiki/Pascal_%28programming_language%29" title="Pascal">Wikipedia article</a> on Pascal contains a good overview of the language if you are interested in investigating further.</p>
|
||||||
|
<p>Pascal became popular for teaching and later became more effective as the language definition changed and more implementations became available.</p>
|
||||||
|
<p>I did not use Pascal much at this time, but later made heavy use of it, as I shall describe in later episodes.</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>University of Manchester:
|
||||||
|
<ul>
|
||||||
|
<li>Wikipedia entry: <a href="http://en.wikipedia.org/wiki/University_of_Manchester">http://en.wikipedia.org/wiki/University_of_Manchester</a></li>
|
||||||
|
<li>Prior to 2004: <a href="https://en.wikipedia.org/wiki/Victoria_University_of_Manchester">https://en.wikipedia.org/wiki/Victoria_University_of_Manchester</a></li>
|
||||||
|
<li>Manchester Museum: <a href="http://en.wikipedia.org/wiki/Manchester_Museum">http://en.wikipedia.org/wiki/Manchester_Museum</a></li>
|
||||||
|
<li>School of Computer Science: <a href="https://en.wikipedia.org/wiki/School_of_Computer_Science,_University_of_Manchester">https://en.wikipedia.org/wiki/School_of_Computer_Science,_University_of_Manchester</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Behavioural Ecology: <a href="https://en.wikipedia.org/wiki/Behavioral_ecology">https://en.wikipedia.org/wiki/Behavioral_ecology</a></li>
|
||||||
|
<li>Experimental animals:
|
||||||
|
<ul>
|
||||||
|
<li>Barbary dove: <a href="https://en.wikipedia.org/wiki/Barbary_dove">https://en.wikipedia.org/wiki/Barbary_dove</a></li>
|
||||||
|
<li>Common Marmoset: <a href="https://en.wikipedia.org/wiki/Common_marmoset">https://en.wikipedia.org/wiki/Common_marmoset</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Current Contents: <a href="https://en.wikipedia.org/wiki/Current_Contents">https://en.wikipedia.org/wiki/Current_Contents</a></li>
|
||||||
|
<li>Card systems:
|
||||||
|
<ul>
|
||||||
|
<li>Index cards: <a href="https://en.wikipedia.org/wiki/Index_card">https://en.wikipedia.org/wiki/Index_card</a></li>
|
||||||
|
<li>Edge-notched cards: <a href="https://en.wikipedia.org/wiki/Edge-notched_card">https://en.wikipedia.org/wiki/Edge-notched_card</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Control Data Corporation CDC 7600: <a href="https://en.wikipedia.org/wiki/CDC_7600">https://en.wikipedia.org/wiki/CDC_7600</a></li>
|
||||||
|
<li>Seymour Cray: <a href="https://en.wikipedia.org/wiki/Seymour_Cray">https://en.wikipedia.org/wiki/Seymour_Cray</a></li>
|
||||||
|
<li>Cray-1 supercomputer: <a href="https://en.wikipedia.org/wiki/Cray-1">https://en.wikipedia.org/wiki/Cray-1</a></li>
|
||||||
|
<li>ICT (ICL) 1900 series: <a href="https://en.wikipedia.org/wiki/ICT_1900_series">https://en.wikipedia.org/wiki/ICT_1900_series</a></li>
|
||||||
|
<li>ICL GEORGE operating system: <a href="http://en.wikipedia.org/wiki/GEORGE_%28operating_system%29">http://en.wikipedia.org/wiki/GEORGE_%28operating_system%29</a></li>
|
||||||
|
<li>Card Punch: <a href="https://en.wikipedia.org/wiki/Keypunch">https://en.wikipedia.org/wiki/Keypunch</a></li>
|
||||||
|
<li>UMRCC videos on YouTube:
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.youtube.com/watch?v=swpE9IS8rso">https://www.youtube.com/watch?v=swpE9IS8rso</a></li>
|
||||||
|
<li><a href="https://www.youtube.com/watch?v=G-ObR4Z6reY">https://www.youtube.com/watch?v=G-ObR4Z6reY</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>The three-day week: <a href="https://en.wikipedia.org/wiki/Three-Day_Week">https://en.wikipedia.org/wiki/Three-Day_Week</a></li>
|
||||||
|
<li>Fortran:
|
||||||
|
<ul>
|
||||||
|
<li>Wikipedia article: <a href="https://en.wikipedia.org/wiki/Fortran">https://en.wikipedia.org/wiki/Fortran</a></li>
|
||||||
|
<li>WikiBooks examples of Fortran: <a href="https://en.wikibooks.org/wiki/Fortran/Fortran_examples#Simple_Fortran_IV_program">https://en.wikibooks.org/wiki/Fortran/Fortran_examples#Simple_Fortran_IV_program</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Graph plotter: <a href="https://en.wikipedia.org/wiki/Plotter">https://en.wikipedia.org/wiki/Plotter</a></li>
|
||||||
|
<li>NAG Library: <a href="https://en.wikipedia.org/wiki/NAG_Numerical_Library">https://en.wikipedia.org/wiki/NAG_Numerical_Library</a></li>
|
||||||
|
<li>The Pascal language:
|
||||||
|
<ul>
|
||||||
|
<li>Wikipedia article: <a href="https://en.wikipedia.org/wiki/Pascal_%28programming_language%29">https://en.wikipedia.org/wiki/Pascal_%28programming_language%29</a></li>
|
||||||
|
<li>"<em>The Programming Language Pascal</em>" 1973: <a href="http://www.standardpascal.com/The_Programming_Language_Pascal_1973.pdf">http://www.standardpascal.com/The_Programming_Language_Pascal_1973.pdf</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><em>Life and Times of a Geek</em> part 1: <a href="http://hackerpublicradio.org/eps.php?id=1664">http://hackerpublicradio.org/eps.php?id=1664</a></li>
|
||||||
|
</ul>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1811/hpr1811_img001.png
Executable file
|
After Width: | Height: | Size: 352 KiB |
BIN
eps/hpr1811/hpr1811_img002.jpg
Executable file
|
After Width: | Height: | Size: 57 KiB |
BIN
eps/hpr1811/hpr1811_img003.jpg
Executable file
|
After Width: | Height: | Size: 62 KiB |
BIN
eps/hpr1811/hpr1811_img004.jpg
Executable file
|
After Width: | Height: | Size: 46 KiB |
BIN
eps/hpr1811/hpr1811_img005.jpg
Executable file
|
After Width: | Height: | Size: 17 KiB |
232
eps/hpr1822/hpr1822_full_shownotes.html
Executable file
@@ -0,0 +1,232 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Some tips on using ImageMagick (HPR Show 1822)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Some tips on using ImageMagick (HPR Show 1822)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#processing-photographs">Processing photographs</a><ul>
|
||||||
|
<li><a href="#stripping-exif-metadata">Stripping EXIF metadata</a></li>
|
||||||
|
<li><a href="#cropping-the-image">Cropping the image</a></li>
|
||||||
|
<li><a href="#reducing-the-image-size">Reducing the image size</a></li>
|
||||||
|
<li><a href="#making-thumbnails">Making thumbnails</a></li>
|
||||||
|
<li><a href="#doing-stuff-to-thumbnails">Doing stuff to thumbnails</a></li>
|
||||||
|
<li><a href="#adding-captions-to-images">Adding captions to images</a></li>
|
||||||
|
<li><a href="#joining-images-together">Joining images together</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<p>I like to use images in HPR shows if I can. I have experimented with various ways of preparing them since I first started contributing, but I'm particularly impressed with what I am able to do using <a href="http://www.imagemagick.org/script/index.php" title="ImageMagick">ImageMagick</a>.</p>
|
||||||
|
<p>The <code>ImageMagick</code> system contains an enormous range of capabilities, enough for a whole series of shows. I though I would talk about some of the features I use when preparing episodes to give you a flavour of what can be done.</p>
|
||||||
|
<p>I'm the rawest amateur when it comes to this kind of image manipulation. Just reading some of the ImageMagick documentation (see <a href="#Links">links</a>) will show you what an enormous number of possibilities there are. I am only using a few in this episode.</p>
|
||||||
|
<h2 id="processing-photographs">Processing photographs</h2>
|
||||||
|
<h3 id="stripping-exif-metadata">Stripping EXIF metadata</h3>
|
||||||
|
<p>I often take pictures on my digital camera and prefer to remove the <a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" title="EXIF">EXIF</a> data from them before uploading. Unfortunately <code>ImageMagick</code> doesn't have a feature designed for doing this. It is possible to use:</p>
|
||||||
|
<pre><code>convert -strip before.jpg after.jpg</code></pre>
|
||||||
|
<p>This is not recommended. However, there is another way of doing this using <a href="http://www.sno.phy.queensu.ca/~phil/exiftool" title="exiftool">exiftool</a>:</p>
|
||||||
|
<pre><code>exiftool -all= image.jpg</code></pre>
|
||||||
|
<p>This saves the original image by appending <code>_original</code> to the filename.</p>
|
||||||
|
<h3 id="cropping-the-image">Cropping the image</h3>
|
||||||
|
<p>I often want to crop the images I produce because there is extraneous stuff in the edges (due to poor photographic technique mostly). It is possible to use ImageMagick to <a href="http://www.imagemagick.org/Usage/crop/" title="ImageMagick crop">crop</a> but often I need to use an interactive method.</p>
|
||||||
|
<p>I used to use the GIMP program to do this, but lately I have found that <a href="https://krita.org/" title="Krita">Krita</a> is a little easier.</p>
|
||||||
|
<h3 id="reducing-the-image-size">Reducing the image size</h3>
|
||||||
|
<p>When preparing an HPR episode I create a directory for all of the files copied off my camera. I often create an <code>images</code> sub-directory drop the pictures there. I use the ImageMagick <code>convert</code> command with the <a href="http://www.imagemagick.org/Usage/resize/" title="ImageMagick resize">-resize</a> option:</p>
|
||||||
|
<pre><code>convert bigpic.jpg -resize 640 smallpic.png</code></pre>
|
||||||
|
<p>This reduces the picture dimensions to 640 pixels wide which fits the HPR web-page better (the aspect ratio is not changed by this operation). The resulting file is also smaller which helps with upload time and server space. The command uses the extension of the output file to determine the resulting format.</p>
|
||||||
|
<p>Typically the <code>images</code> directory contains the pictures from my camera which have been converted with <code>exiftool</code>. They have the extension <code>.JPG</code>. I run the following command to convert them all:</p>
|
||||||
|
<pre><code>for f in images/*.JPG; do t=${f##*/}; convert $f -resize 640 ${t%.JPG}.png; done</code></pre>
|
||||||
|
<p>This traverses all images. The variable <code>t</code> contains the filename without the directory because I want the new files to be saved in the parent directory. In the <code>convert</code> command the output file is specified with variable <code>t</code> which has had the <code>.JPG</code> stripped from the end and replaced with <code>.png</code>.</p>
|
||||||
|
<p>It's also possible to reduce the image by a percentage rather than trying to reduce to specific dimensions. In this case, the value after <code>-resize</code> would be a percentage such as <code>50%</code>.</p>
|
||||||
|
<p>I should really write a script to do this stage of image processing but I have not yet done so.</p>
|
||||||
|
<h3 id="making-thumbnails">Making thumbnails</h3>
|
||||||
|
<p>Sometimes, if there are many pictures, I generate <a href="http://www.imagemagick.org/Usage/thumbnails/" title="ImageMagick thumbnails">thumbnail</a> images for the notes which I can set up to be clickable to get to the bigger image. I would usually make a directory <code>thumbs</code> to hold the thumbnails. The command to make a single thumbnail is:</p>
|
||||||
|
<pre><code>mogrify -format png -path thumbs -thumbnail 100x100 image.png</code></pre>
|
||||||
|
<p>I have a script for doing this:</p>
|
||||||
|
<pre><code>#!/bin/bash
|
||||||
|
#
|
||||||
|
# Simple script to generate thumbnail images
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# We expect there to be a file called 'manifest' containing the names of the
|
||||||
|
# images we want to build thumbnails for.
|
||||||
|
#
|
||||||
|
if [[ ! -e manifest ]]; then
|
||||||
|
echo "Expected a manifest file, but none exists"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# We might need to create the sub-directory
|
||||||
|
#
|
||||||
|
if [[ ! -e thumbs ]]; then
|
||||||
|
mkdir thumbs
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Process the files in the manifest, making thumbnails
|
||||||
|
#
|
||||||
|
for f in $(cat manifest); do
|
||||||
|
mogrify -format png -path thumbs -thumbnail 100x100 $f
|
||||||
|
done
|
||||||
|
|
||||||
|
exit</code></pre>
|
||||||
|
<p>You can use the <code>mogrify</code> command to do the whole thing without the script with a command such as the following:</p>
|
||||||
|
<pre><code>mogrify -format png -path thumbs -thumbnail 100x100 *.png</code></pre>
|
||||||
|
<p>This assumes you want to generate thumbnails for all images in the current directory, but this is not always what I want to do.</p>
|
||||||
|
<h3 id="doing-stuff-to-thumbnails">Doing stuff to thumbnails</h3>
|
||||||
|
<p>In an HPR episode I've been creating recently I added a border and a watermark to my thumbnails. I did it this way:</p>
|
||||||
|
<pre><code>#!/bin/bash
|
||||||
|
#
|
||||||
|
# Simple script to add a number "watermark" and a border to a collection of
|
||||||
|
# thumbnails. Run it in the parent directory.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# We expect there to be a file called 'manifest' containing the names of the
|
||||||
|
# thumbnails. These are the same names as the main images but in the 'thumbs'
|
||||||
|
# directory
|
||||||
|
#
|
||||||
|
if [[ ! -e manifest ]]; then
|
||||||
|
echo "Expected a manifest file, but none exists"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Check there are thumbnails
|
||||||
|
#
|
||||||
|
if [[ ! -e thumbs ]]; then
|
||||||
|
echo "No 'thumbs' directory, can't continue"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
i=1
|
||||||
|
|
||||||
|
#
|
||||||
|
# Process the thumbnails
|
||||||
|
#
|
||||||
|
for f in $(cat manifest); do
|
||||||
|
#
|
||||||
|
# Save the original file
|
||||||
|
#
|
||||||
|
o="${f%%.*}_orig.png"
|
||||||
|
mv thumbs/$f thumbs/$o
|
||||||
|
|
||||||
|
#
|
||||||
|
# Convert the original adding a numeric "watermark" and creating the
|
||||||
|
# original name again
|
||||||
|
#
|
||||||
|
convert thumbs/$o -font Courier -pointsize 20 \
|
||||||
|
-draw "gravity center \
|
||||||
|
fill black text 0,12 '$i' \
|
||||||
|
fill white text 1,11 '$i'" \
|
||||||
|
thumbs/$f
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add a border into the same file
|
||||||
|
#
|
||||||
|
convert thumbs/$f -shave 1x1 -bordercolor black -border 1 thumbs/$f
|
||||||
|
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
|
||||||
|
exit</code></pre>
|
||||||
|
<h3 id="adding-captions-to-images">Adding captions to images</h3>
|
||||||
|
<p>Also, in an HPR show I'm currently putting together I decided to try adding captions to pictures. I made a file containing the names of the image files followed by the caption.</p>
|
||||||
|
<p>Here's the rough and ready script I made to do this:</p>
|
||||||
|
<pre><code>#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rudimentary script to add captions to images
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# The captions are in the file 'captions' so check it exists
|
||||||
|
#
|
||||||
|
if [[ ! -e captions ]]; then
|
||||||
|
echo "Missing 'captions' file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read lines from the captions file (use 'read' to prevent Bash treating
|
||||||
|
# spaces as argument delimiters)
|
||||||
|
#
|
||||||
|
while read l; do
|
||||||
|
#
|
||||||
|
# Split the line into filename and caption on the comma
|
||||||
|
#
|
||||||
|
f=${l%%,*}
|
||||||
|
c=${l##*,}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Save the original file
|
||||||
|
#
|
||||||
|
o="${f%%.*}_orig.png"
|
||||||
|
mv $f $o
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add the caption making the new file have the original name
|
||||||
|
#
|
||||||
|
convert $o -background Khaki label:"$c" -gravity Center -append $f
|
||||||
|
|
||||||
|
done < captions
|
||||||
|
|
||||||
|
exit</code></pre>
|
||||||
|
<p>The <code>captions</code> file has lines like this:</p>
|
||||||
|
<pre><code>Flours_used.png,Flours used in the demonstration
|
||||||
|
Kenwood_Chef.png,Kenwood Chef and accessories</code></pre>
|
||||||
|
<p>This is not elegant or very robust but it did the job. Feel free to develop this further if you want.</p>
|
||||||
|
<h3 id="joining-images-together">Joining images together</h3>
|
||||||
|
<p>Again, while preparing an HPR show I wanted to do some unusual image manipulation. This time I wanted to shrink two images and join them together side by side to make a final image. The shrinking was no problem, as we have already seen, but I searched for an answer to the join question and found the following:</p>
|
||||||
|
<pre><code>convert -background '#FFF9E3' xc:none -resize 200x1\! left.png -append right.png -gravity south +append +repage joined.png</code></pre>
|
||||||
|
<p>However, a better method is to use <a href="http://www.imagemagick.org/Usage/montage/" title="ImageMagick montage">montage</a> such as:</p>
|
||||||
|
<pre><code>montage -background '#000000' -geometry 1x1\<+1+1 left.png right.png montage.png</code></pre>
|
||||||
|
<p>This tiles the two images on a black background. The <code>-geometry</code> option defines how big the tiles are and how much border space to leave. The special <code>1x1\<</code> sequence makes ImageMagick find the best fit - it keeps the images the same size as the originals.</p>
|
||||||
|
<p>I don't really understand how the <code>convert</code> example works. I found it at <a href="https://stackoverflow.com/questions/12076293/combine-2-images-side-by-side-into-1-with-imagemagick-php" class="uri">https://stackoverflow.com/questions/12076293/combine-2-images-side-by-side-into-1-with-imagemagick-php</a>. The <code>montage</code> example is a little more straightforward.</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>EXIF Wikipedia page: <a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" class="uri">https://en.wikipedia.org/wiki/Exchangeable_image_file_format</a></li>
|
||||||
|
<li>exiftool: <a href="http://www.sno.phy.queensu.ca/~phil/exiftool" class="uri">http://www.sno.phy.queensu.ca/~phil/exiftool</a></li>
|
||||||
|
<li>Krita: <a href="https://krita.org/" class="uri">https://krita.org/</a></li>
|
||||||
|
<li>ImageMagick:
|
||||||
|
<ul>
|
||||||
|
<li>Main site: <a href="http://www.imagemagick.org/script/index.php" class="uri">http://www.imagemagick.org/script/index.php</a></li>
|
||||||
|
<li>Crop: <a href="http://www.imagemagick.org/Usage/crop/" class="uri">http://www.imagemagick.org/Usage/crop/</a></li>
|
||||||
|
<li>Resize: <a href="http://www.imagemagick.org/Usage/resize/" class="uri">http://www.imagemagick.org/Usage/resize/</a></li>
|
||||||
|
<li>Thumbnails: <a href="http://www.imagemagick.org/Usage/thumbnails/" class="uri">http://www.imagemagick.org/Usage/thumbnails/</a></li>
|
||||||
|
<li>Montage: <a href="http://www.imagemagick.org/Usage/montage/" class="uri">http://www.imagemagick.org/Usage/montage/</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1827/hpr1827_Damp_teatowel_is_best.png
Executable file
|
After Width: | Height: | Size: 327 KiB |
BIN
eps/hpr1827/hpr1827_Damp_teatowel_is_best_tn.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
eps/hpr1827/hpr1827_Dividing_the_dough.png
Executable file
|
After Width: | Height: | Size: 489 KiB |
BIN
eps/hpr1827/hpr1827_Dividing_the_dough_tn.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
eps/hpr1827/hpr1827_Dough_being_kneaded.png
Executable file
|
After Width: | Height: | Size: 321 KiB |
BIN
eps/hpr1827/hpr1827_Dough_being_kneaded_tn.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
eps/hpr1827/hpr1827_Dough_is_now_pliable.png
Executable file
|
After Width: | Height: | Size: 198 KiB |
BIN
eps/hpr1827/hpr1827_Dough_is_now_pliable_tn.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
eps/hpr1827/hpr1827_Dough_mixed_in_mixer.png
Executable file
|
After Width: | Height: | Size: 503 KiB |
BIN
eps/hpr1827/hpr1827_Dough_mixed_in_mixer_tn.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
eps/hpr1827/hpr1827_Dough_pressed_down.png
Executable file
|
After Width: | Height: | Size: 481 KiB |
BIN
eps/hpr1827/hpr1827_Dough_pressed_down_tn.png
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
eps/hpr1827/hpr1827_Dough_ready_to_rise.png
Executable file
|
After Width: | Height: | Size: 438 KiB |
BIN
eps/hpr1827/hpr1827_Dough_ready_to_rise_tn.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
eps/hpr1827/hpr1827_Dried_yeast_activating.png
Executable file
|
After Width: | Height: | Size: 541 KiB |
BIN
eps/hpr1827/hpr1827_Dried_yeast_activating_tn.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
eps/hpr1827/hpr1827_Flour_and_salt.png
Executable file
|
After Width: | Height: | Size: 574 KiB |
BIN
eps/hpr1827/hpr1827_Flour_and_salt_tn.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
eps/hpr1827/hpr1827_Flours_used.png
Executable file
|
After Width: | Height: | Size: 250 KiB |
BIN
eps/hpr1827/hpr1827_Flours_used_tn.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
eps/hpr1827/hpr1827_In_greased_loaf_tins.png
Executable file
|
After Width: | Height: | Size: 302 KiB |
BIN
eps/hpr1827/hpr1827_In_greased_loaf_tins_tn.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
eps/hpr1827/hpr1827_Ingredients_mixing.png
Executable file
|
After Width: | Height: | Size: 458 KiB |
BIN
eps/hpr1827/hpr1827_Ingredients_mixing_tn.png
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
eps/hpr1827/hpr1827_Kenwood_Chef.png
Executable file
|
After Width: | Height: | Size: 342 KiB |
BIN
eps/hpr1827/hpr1827_Kenwood_Chef_tn.png
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
eps/hpr1827/hpr1827_Knocking_back.png
Executable file
|
After Width: | Height: | Size: 231 KiB |
BIN
eps/hpr1827/hpr1827_Knocking_back_tn.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
eps/hpr1827/hpr1827_Loaves_baked.png
Executable file
|
After Width: | Height: | Size: 486 KiB |
BIN
eps/hpr1827/hpr1827_Loaves_baked_tn.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
eps/hpr1827/hpr1827_Loaves_cooling.png
Executable file
|
After Width: | Height: | Size: 360 KiB |
BIN
eps/hpr1827/hpr1827_Loaves_cooling_tn.png
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
eps/hpr1827/hpr1827_Ready_for_tins.png
Executable file
|
After Width: | Height: | Size: 258 KiB |
BIN
eps/hpr1827/hpr1827_Ready_for_tins_tn.png
Executable file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
eps/hpr1827/hpr1827_Risen_dough.png
Executable file
|
After Width: | Height: | Size: 542 KiB |
BIN
eps/hpr1827/hpr1827_Risen_dough_in_tins.png
Executable file
|
After Width: | Height: | Size: 388 KiB |
BIN
eps/hpr1827/hpr1827_Risen_dough_in_tins_tn.png
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
eps/hpr1827/hpr1827_Risen_dough_tn.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
eps/hpr1827/hpr1827_Rising_in_tins.png
Executable file
|
After Width: | Height: | Size: 322 KiB |
BIN
eps/hpr1827/hpr1827_Rising_in_tins_tn.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
eps/hpr1827/hpr1827_Sliced_loaf.png
Executable file
|
After Width: | Height: | Size: 564 KiB |
BIN
eps/hpr1827/hpr1827_Sliced_loaf_tn.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
eps/hpr1827/hpr1827_Wholemeal_Bread_Recipe.pdf
Executable file
100
eps/hpr1827/hpr1827_full_shownotes.html
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>How I make bread (HPR Show 1827)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">How I make bread (HPR Show 1827)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#introduction">Introduction</a></li>
|
||||||
|
<li><a href="#recipe">Recipe</a><ul>
|
||||||
|
<li><a href="#flour">Flour</a></li>
|
||||||
|
<li><a href="#food-mixer">Food Mixer</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#preparation">Preparation</a><ul>
|
||||||
|
<li><a href="#mixing-and-kneading">Mixing and kneading</a></li>
|
||||||
|
<li><a href="#rising">Rising</a></li>
|
||||||
|
<li><a href="#knocking-back">Knocking back</a></li>
|
||||||
|
<li><a href="#second-rise">Second rise</a></li>
|
||||||
|
<li><a href="#baking-the-bread">Baking the bread</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#conclusion">Conclusion</a></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="introduction">Introduction</h2>
|
||||||
|
<p>Ken Fallon was asking for bread-making advice on a recent <em>Community News</em> recording. I've been making my own bread since the 1970's and I thought I'd share my methods in response. <a href="http://hackerpublicradio.org/correspondents.php?hostid=195">Frank Bell</a> also did an excellent <a href="http://hackerpublicradio.org/eps.php?id=1327">bread-making episode</a> in 2013.</p>
|
||||||
|
<p>I remember my mother having a go at making bread when I was a kid. The result smelled lovely but the bread hadn't risen much; it had an unfortunate resemblance to a brick in shape, if I remember right, but it was delicious nevertheless. After that I always wanted to try making my own.</p>
|
||||||
|
<p>I bought a <a href="https://en.wikipedia.org/wiki/Kenwood_Chef" title="Kenwood Chef">Kenwood Chef</a> mixer when I lived in Lancaster in the 1970's and it came with some bread recipes, which I tried. I found I had a fair degree of success in the first instance, though there were a number of failures. I kept experimenting and got better at it, and graduated to making loaves of various types, rolls, bagels, pitta bread, pizza bases, and so on.</p>
|
||||||
|
<p>I have been making my own bread ever since. I continued with the method of using the Kenwood Chef and I'm currently on my third one. Eventually the gear box breaks I have found. Making bread, especially large loaves, works the device very hard.</p>
|
||||||
|
<p>As life got busier I bought a bread-maker to simplify the process, and am currently on my third one of these a <a href="http://www.chrisrand.com/panasonic-SD255-breadmaker-bread-maker/" title="Panasonic SD255">Panasonic SD255</a> (now discontinued). I mainly use this device because it is so simple to prepare the ingredients and then leave the bread to mix, rise and bake.</p>
|
||||||
|
<h2 id="recipe">Recipe</h2>
|
||||||
|
<p>For this episode I baked two 1lb loaves according to an old recipe I have been using for many years. It is based on one which came with the Kenwood Chef and uses wholemeal flour. I have included a PDF copy of this <a href="hpr1827_Wholemeal_Bread_Recipe.pdf">recipe</a>, see the links below.</p>
|
||||||
|
<h3 id="flour">Flour</h3>
|
||||||
|
<p><a href="hpr1827_Flours_used.png"><img src="hpr1827_Flours_used_tn.png" alt="Flours used" /></a><br />Flours used for this episode (click the thumbnail for the full image)</p>
|
||||||
|
<p>For these loaves I used a mixture of strong plain wholemeal flour and strong plain white. Probably used about 80% wholemeal, 20% white.</p>
|
||||||
|
<p>Names of flours vary between countries; <em>strong plain</em>, the term used in the UK, means a high-gluten flour, often from a winter wheat variety, without raising agent. High-gluten wheats are sometimes referred to as <em>hard</em>. The gluten makes a more elastic dough which rises better and makes a more chewy bread.</p>
|
||||||
|
<h3 id="food-mixer">Food Mixer</h3>
|
||||||
|
<p><a href="hpr1827_Kenwood_Chef.png"><img src="hpr1827_Kenwood_Chef_tn.png" alt="Kenwood Chef" /></a><br />Kenwood Chef</p>
|
||||||
|
<p>The picture shows my mixer, which I have had for around 20 years. This model is not made any more. I have a number of attachments for it including the ones pictured, as well as a coffee grinder and a wheat mill.</p>
|
||||||
|
<h2 id="preparation">Preparation</h2>
|
||||||
|
<p><a href="hpr1827_Flour_and_salt.png"><img src="hpr1827_Flour_and_salt_tn.png" alt="Flour and salt" /></a> <a href="hpr1827_Dried_yeast_activating.png"><img src="hpr1827_Dried_yeast_activating_tn.png" alt="Dried yeast activating" /></a><br />Flour and salt. Activating the dried yeast</p>
|
||||||
|
<p>I normally used this dried yeast when making bread this way. It needs to be mixed into warm water with some sugar to feed it. This combination makes it begin to froth quite quickly, especially on a warm day.</p>
|
||||||
|
<p>I have used fresh yeast when I can get it, but it's not sold anywhere nearby any more. Fresh yeast also needs to be activated before mixing.</p>
|
||||||
|
<h3 id="mixing-and-kneading">Mixing and kneading</h3>
|
||||||
|
<p><a href="hpr1827_Ingredients_mixing.png"><img src="hpr1827_Ingredients_mixing_tn.png" alt="Ingredients mixing" /></a> <a href="hpr1827_Dough_mixed_in_mixer.png"><img src="hpr1827_Dough_mixed_in_mixer_tn.png" alt="Dough mixed in mixer" /></a> <a href="hpr1827_Dough_being_kneaded.png"><img src="hpr1827_Dough_being_kneaded_tn.png" alt="Dough being kneaded by hand" /></a> <a href="hpr1827_Dough_is_now_pliable.png"><img src="hpr1827_Dough_is_now_pliable_tn.png" alt="Dough is now pliable" /></a><br />Making dough</p>
|
||||||
|
<p>I find the ingredients (water+sugar, yeast, flour, salt and oil) mix together without problems in the food mixer and soon start to form a dough. I let the mixer work the dough for the recommended 3 minutes and get an end product that you can see in picture 6. I like to finish off the kneading by hand to make sure the dough is as elastic as it can be.</p>
|
||||||
|
<h3 id="rising">Rising</h3>
|
||||||
|
<p><a href="hpr1827_Dough_ready_to_rise.png"><img src="hpr1827_Dough_ready_to_rise_tn.png" alt="Dough ready to rise" /></a> <a href="hpr1827_Damp_teatowel_is_best.png"><img src="hpr1827_Damp_teatowel_is_best_tn.png" alt="Damp teatowel is best" /></a><br />Leaving the dough to rise</p>
|
||||||
|
<p>You can cover the bowl with film but unless the film has been greased the dough can stick to it. I forgot that at the start, and transferred to a damp teatowel once I had remembered.</p>
|
||||||
|
<h3 id="knocking-back">Knocking back</h3>
|
||||||
|
<p><a href="hpr1827_Risen_dough.png"><img src="hpr1827_Risen_dough_tn.png" alt="Risen dough" /></a> <a href="hpr1827_Knocking_back.png"><img src="hpr1827_Knocking_back_tn.png" alt="Knocking back" /></a><br />Second kneading</p>
|
||||||
|
<p>I use the food mixer at this stage, though kneading by hand would work just as well.</p>
|
||||||
|
<h3 id="second-rise">Second rise</h3>
|
||||||
|
<p><a href="hpr1827_Dividing_the_dough.png"><img src="hpr1827_Dividing_the_dough_tn.png" alt="Dividing the dough" /></a> <a href="hpr1827_Ready_for_tins.png"><img src="hpr1827_Ready_for_tins_tn.png" alt="Ready for tins" /></a> <a href="hpr1827_In_greased_loaf_tins.png"><img src="hpr1827_In_greased_loaf_tins_tn.png" alt="In greased loaf tins" /></a> <a href="hpr1827_Dough_pressed_down.png"><img src="hpr1827_Dough_pressed_down_tn.png" alt="Dough pressed down" /></a> <a href="hpr1827_Rising_in_tins.png"><img src="hpr1827_Rising_in_tins_tn.png" alt="Rising in tins" /></a><br />Dividing, placing in tins and leaving to rise again</p>
|
||||||
|
<p>I divide the dough with the tool in picture 13 which is a dough scraper for use with the wet doughs produced by some recipes. I'm using some rather old bread tins described as <em>1lb loaf tins</em>. Make sure to grease them well before placing the dough inside or it will be hard work to extract the loaves.</p>
|
||||||
|
<p>I pressed the dough into the tins this time, but it shouldn't really be necessary and the end result will be better if you don't. Leave covered to rise as before.</p>
|
||||||
|
<h3 id="baking-the-bread">Baking the bread</h3>
|
||||||
|
<p><a href="hpr1827_Risen_dough_in_tins.png"><img src="hpr1827_Risen_dough_in_tins_tn.png" alt="Risen dough in tins" /></a> <a href="hpr1827_Loaves_baked.png"><img src="hpr1827_Loaves_baked_tn.png" alt="Loaves baked" /></a> <a href="hpr1827_Loaves_cooling.png"><img src="hpr1827_Loaves_cooling_tn.png" alt="Loaves cooling" /></a> <a href="hpr1827_Sliced_loaf.png"><img src="hpr1827_Sliced_loaf_tn.png" alt="Sliced loaf" /></a><br />Once risen, bake the bread, leave to cool and voila! fresh bread</p>
|
||||||
|
<p>I like to slice up my bread after it's cooled, then I freeze it. That way I can take out individual slices and toast them from frozen or allow them to thaw and use them.</p>
|
||||||
|
<h2 id="conclusion">Conclusion</h2>
|
||||||
|
<p>My favourite recipe at the moment uses a Wholemeal, Rye and Spelt flour mixture with sunflower seeds. This makes quite a heavy bread which is absolutely wonderful toasted. I do cheat a little though, and make this in my bread-maker!</p>
|
||||||
|
<p>Flours like Rye are low in gluten, so breads made with them do not rise as well. I have also been experimenting with Buckwheat flour, which I don't think has very much gluten either, and I mix this (and rye flour) with other flours to get a reasonable dough.</p>
|
||||||
|
<p>My son, who used to help with the bread-making as a little boy, makes a very good sourdough loaf and often asks me to look after his sourdough starter when he's away on holiday. I haven't had great success with sourdough and need to get some lessons from him.</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Frank Bell's HPR episode on bread making: <a href="http://hackerpublicradio.org/eps.php?id=1327" class="uri">http://hackerpublicradio.org/eps.php?id=1327</a></li>
|
||||||
|
<li>Kenwood Chef: <a href="https://en.wikipedia.org/wiki/Kenwood_Chef" class="uri">https://en.wikipedia.org/wiki/Kenwood_Chef</a></li>
|
||||||
|
<li>Panasonic SD255: <a href="http://www.chrisrand.com/panasonic-SD255-breadmaker-bread-maker/" class="uri">http://www.chrisrand.com/panasonic-SD255-breadmaker-bread-maker/</a></li>
|
||||||
|
<li>Wholemeal bread recipe: <a href="hpr1827_Wholemeal_Bread_Recipe.pdf" class="uri">hpr1827_Wholemeal_Bread_Recipe.pdf</a></li>
|
||||||
|
</ul>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
250
eps/hpr1843/hpr1843_full_shownotes.html
Executable file
@@ -0,0 +1,250 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Some Bash tips (HPR Show 1843)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Some Bash tips (HPR Show 1843)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#introduction">Introduction</a><ul>
|
||||||
|
<li><a href="#basic-usage">Basic Usage</a><ul>
|
||||||
|
<li><a href="#pushd-dir">pushd <em>dir</em></a></li>
|
||||||
|
<li><a href="#popd">popd</a></li>
|
||||||
|
<li><a href="#dirs">dirs</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#more-advanced-usage">More advanced usage</a><ul>
|
||||||
|
<li><a href="#pushd">pushd</a></li>
|
||||||
|
<li><a href="#popd-1">popd</a></li>
|
||||||
|
<li><a href="#dirs-1">dirs</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#conclusion">Conclusion</a></li>
|
||||||
|
<li><a href="#manual-pages">Manual Pages</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h1 id="introduction">Introduction</h1>
|
||||||
|
<p>If you are a command-line user (and personally, I think you should be prepared to use the command line since it's so powerful) it's likely that you have used the <code>cd</code> command to change directory.</p>
|
||||||
|
<p>There are other directory movement commands within Bash: <code>pushd</code>, <code>popd</code> and <code>dirs</code>. I'm going to describe these today.</p>
|
||||||
|
<h2 id="basic-usage">Basic Usage</h2>
|
||||||
|
<h3 id="pushd-dir">pushd <em>dir</em></h3>
|
||||||
|
<p>This command changes directory like <code>cd</code> but it does more, it saves the previous directory, and the directory you've moved to, in a stack.</p>
|
||||||
|
<p>So, assume you have logged in and are in your home directory:</p>
|
||||||
|
<pre><code>dave@i7:~$ pwd
|
||||||
|
/home/dave
|
||||||
|
dave@i7:~$ pushd Documents
|
||||||
|
~/Documents ~
|
||||||
|
dave@i7:~/Documents$</code></pre>
|
||||||
|
<p><code>pwd</code> shows the directory I'm in. The <code>pushd Documents</code> takes me to my <code>Documents</code> directory. The stack is shown as a list, with the top of the stack on the left, and with <code>~</code> denoting the top-level home directory.</p>
|
||||||
|
<h3 id="popd">popd</h3>
|
||||||
|
<p>This command moves back to the previous directory. To be more precise, it takes the top-most (current) directory off the stack and changes directory to the new top.</p>
|
||||||
|
<p>So, continuing with the previous example:</p>
|
||||||
|
<pre><code>dave@i7:~/Documents$ popd
|
||||||
|
~
|
||||||
|
dave@i7:~$ pwd
|
||||||
|
/home/dave
|
||||||
|
dave@i7:~$</code></pre>
|
||||||
|
<p>So, <code>popd</code> has moved me back to the previous directory.</p>
|
||||||
|
<h3 id="dirs">dirs</h3>
|
||||||
|
<p>This command displays the directory stack. By default the stack is shown as a left-to-right list as we have seen in the above examples.</p>
|
||||||
|
<p>Using the <code>-v</code> option to <code>dirs</code> shows the stack as a vertical list with index numbers:</p>
|
||||||
|
<pre><code>dave@i7:~$ pushd Documents
|
||||||
|
~/Documents ~
|
||||||
|
dave@i7:~/Documents$ pushd subdir
|
||||||
|
~/Documents/subdir ~/Documents ~
|
||||||
|
dave@i7:~/Documents/subdir$ dirs -v
|
||||||
|
0 ~/Documents/subdir
|
||||||
|
1 ~/Documents
|
||||||
|
2 ~
|
||||||
|
dave@i7:~/Documents/subdir$</code></pre>
|
||||||
|
<p>Note that the directory stack is shown after each <code>pushd</code> but <code>dir -v</code> shows it vertically. The numbering begins with zero.</p>
|
||||||
|
<h2 id="more-advanced-usage">More advanced usage</h2>
|
||||||
|
<p>The three commands we have looked have a lot more features than we've seen so far.</p>
|
||||||
|
<h3 id="pushd">pushd</h3>
|
||||||
|
<p>A slightly modified version of the <code>pushd</code> manpage is available at the bottom of this document.</p>
|
||||||
|
<p>You can use <code>pushd</code> to add a directory to the stack without changing to that directory. To do this use the <code>-n</code> option:</p>
|
||||||
|
<pre><code>dave@i7:~$ pushd -n ~/Documents
|
||||||
|
~ ~/Documents
|
||||||
|
dave@i7:~$ pushd -n ~/Documents/subdir
|
||||||
|
~ ~/Documents/subdir ~/Documents
|
||||||
|
dave@i7:~$ dirs -v
|
||||||
|
0 ~
|
||||||
|
1 ~/Documents/subdir
|
||||||
|
2 ~/Documents</code></pre>
|
||||||
|
<p>Note that the order of the stack is not the same as it would have been if I had visited the directories in the same order.</p>
|
||||||
|
<p>You can manipulate the stack with <code>pushd</code> but the order is fixed, you can only rotate it (as if it's a loop) so that a particular directory is on top. This is done by using the option <code>+n</code> or <code>-n</code> (where <code>n</code> is an integer number not the letter 'n'). So <code>pushd +2</code> means to rotate the stack so that entry number 2 counting from the left (or as numbered by <code>dirs -v</code>) is raised to the top. Everything else rotates appropriately:</p>
|
||||||
|
<pre><code>dave@i7:~$ pushd +2
|
||||||
|
~/Documents ~ ~/Documents/subdir
|
||||||
|
dave@i7:~/Documents$ dirs -v
|
||||||
|
0 ~/Documents
|
||||||
|
1 ~
|
||||||
|
2 ~/Documents/subdir</code></pre>
|
||||||
|
<p>On the other hand <code>pushd -0</code> rotates the stack but numbers the elements from the right (not the same as <code>dirs -v</code>):</p>
|
||||||
|
<pre><code>dave@i7:~/Documents$ pushd -0
|
||||||
|
~/Documents/subdir ~/Documents ~
|
||||||
|
dave@i7:~/Documents/subdir$ dirs -p
|
||||||
|
~/Documents/subdir
|
||||||
|
~/Documents
|
||||||
|
~</code></pre>
|
||||||
|
<p>Element 0 was the directory <code>~/Documents/subdir</code> which is now at the top.</p>
|
||||||
|
<h3 id="popd-1">popd</h3>
|
||||||
|
<p>A slightly modified version of the <code>popd</code> manpage is available at the bottom of this document.</p>
|
||||||
|
<p>As with <code>pushd</code> <code>popd</code> also takes a <code>-n</code> option which manipulates the stack without changing the current directory.</p>
|
||||||
|
<pre><code>dave@i7:~$ pushd Documents/
|
||||||
|
~/Documents ~
|
||||||
|
dave@i7:~/Documents$ pushd subdir/
|
||||||
|
~/Documents/subdir ~/Documents ~
|
||||||
|
dave@i7:~/Documents/subdir$ popd -n
|
||||||
|
~/Documents/subdir ~
|
||||||
|
dave@i7:~/Documents/subdir$ popd
|
||||||
|
~
|
||||||
|
dave@i7:~$</code></pre>
|
||||||
|
<p>Note that <code>popd -n</code> removed element 1 from the stack whereas plain <code>popd</code> removed the zeroth element.</p>
|
||||||
|
<p>As with <code>pushd</code> options of the form <code>+n</code> and <code>-n</code> are catered for (where <code>n</code> is an integer number not the letter 'n'). In this case <code>popd +3</code> means to remove directory 3 from the stack, counting from the left, whereas <code>popd -2</code> counts from the right.</p>
|
||||||
|
<p>This is not stack rotation as with <code>pushd</code> but deletion of elements. If the topmost element is deleted then the current directory changes:</p>
|
||||||
|
<pre><code># Traverse several directories
|
||||||
|
dave@i7:~$ pushd Test1
|
||||||
|
~/Test1 ~
|
||||||
|
dave@i7:~/Test1$ pushd Test2
|
||||||
|
~/Test1/Test2 ~/Test1 ~
|
||||||
|
dave@i7:~/Test1/Test2$ pushd Test3
|
||||||
|
~/Test1/Test2/Test3 ~/Test1/Test2 ~/Test1 ~
|
||||||
|
dave@i7:~/Test1/Test2/Test3$ pushd Test4
|
||||||
|
~/Test1/Test2/Test3/Test4 ~/Test1/Test2/Test3 ~/Test1/Test2 ~/Test1 ~
|
||||||
|
dave@i7:~/.../Test2/Test3/Test4$ dirs -v
|
||||||
|
0 ~/Test1/Test2/Test3/Test4
|
||||||
|
1 ~/Test1/Test2/Test3
|
||||||
|
2 ~/Test1/Test2
|
||||||
|
3 ~/Test1
|
||||||
|
4 ~
|
||||||
|
# Now use popd +n to remove numbered directories
|
||||||
|
dave@i7:~/.../Test2/Test3/Test4$ popd +3
|
||||||
|
~/Test1/Test2/Test3/Test4 ~/Test1/Test2/Test3 ~/Test1/Test2 ~
|
||||||
|
dave@i7:~/.../Test2/Test3/Test4$ popd +0
|
||||||
|
~/Test1/Test2/Test3 ~/Test1/Test2 ~
|
||||||
|
dave@i7:~/Test1/Test2/Test3$ dirs -v
|
||||||
|
0 ~/Test1/Test2/Test3
|
||||||
|
1 ~/Test1/Test2
|
||||||
|
2 ~
|
||||||
|
# Then use popd -n to count from the bottom of the stack
|
||||||
|
dave@i7:~/Test1/Test2/Test3$ popd -1
|
||||||
|
~/Test1/Test2/Test3 ~
|
||||||
|
dave@i7:~/Test1/Test2/Test3$ dirs -v
|
||||||
|
0 ~/Test1/Test2/Test3
|
||||||
|
1 ~</code></pre>
|
||||||
|
<h3 id="dirs-1">dirs</h3>
|
||||||
|
<p>A slightly modified version of the <code>dirs</code> manpage is available below.</p>
|
||||||
|
<p>I have been using <code>dirs -v</code> to show the directory stack in what I think is a more readable form, but there are other options.</p>
|
||||||
|
<p>The <code>-p</code> option prints stack entries one per line without numbering. The <code>-l</code> option gives the full pathname without the tilde ('~') at the start denoting the home directory.</p>
|
||||||
|
<p>You can clear the entire directory stack using the <code>-c</code> option.</p>
|
||||||
|
<p>If you are thoroughly confused by the <code>+n</code> and <code>-n</code> options then using them with <code>dirs</code> makes it plainer what directories they refer to:</p>
|
||||||
|
<pre><code>dave@i7:~$ cd; pushd Test1; pushd Test2; pushd Test3; pushd Test4
|
||||||
|
~/Test1 ~
|
||||||
|
~/Test1/Test2 ~/Test1 ~
|
||||||
|
~/Test1/Test2/Test3 ~/Test1/Test2 ~/Test1 ~
|
||||||
|
~/Test1/Test2/Test3/Test4 ~/Test1/Test2/Test3 ~/Test1/Test2 ~/Test1 ~
|
||||||
|
|
||||||
|
dave@i7:~/.../Test2/Test3/Test4$ dirs +3
|
||||||
|
~/Test1
|
||||||
|
dave@i7:~/.../Test2/Test3/Test4$ dirs -v
|
||||||
|
0 ~/Test1/Test2/Test3/Test4
|
||||||
|
1 ~/Test1/Test2/Test3
|
||||||
|
2 ~/Test1/Test2
|
||||||
|
3 ~/Test1
|
||||||
|
4 ~
|
||||||
|
dave@i7:~/.../Test2/Test3/Test4$ dirs -3
|
||||||
|
~/Test1/Test2/Test3</code></pre>
|
||||||
|
<h1 id="conclusion">Conclusion</h1>
|
||||||
|
<p>What use are these commands?</p>
|
||||||
|
<p>I used to use them a lot pre-Linux when I was working on older Unix systems like Ultrix, SunOS and Solaris. This was in the days of real terminals and before the days of tabbed terminal emulators and virtual desktops.</p>
|
||||||
|
<p>I used to find it very helpful to be able to stop what I was doing in one directory and <code>pushd</code> to another to check something or answer a question, then <code>popd</code> back where I came from.</p>
|
||||||
|
<p>Nowadays I tend to have several terminal emulators on several virtual desktops and each has multiple tabs, so I use them to separate out my directories. However, if you tend to work in a single terminal session you might like I used to you might find them useful.</p>
|
||||||
|
<hr />
|
||||||
|
<h1 id="manual-pages">Manual Pages</h1>
|
||||||
|
<p><strong>pushd [-n] [+n] [-n]</strong></p>
|
||||||
|
<p><strong>pushd [-n] [dir]</strong></p>
|
||||||
|
<p>Adds a directory to the top of the directory stack, or rotates the stack, making the new top of the stack the current working directory. With no arguments, exchanges the top two directories and returns 0, unless the directory stack is empty. Arguments, if supplied, have the following meanings:</p>
|
||||||
|
<dl>
|
||||||
|
<dt><strong>-n</strong> (a hyphen and the letter 'n')</dt>
|
||||||
|
<dd>Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>+<em>n</em></strong> (a plus and an integer)</dt>
|
||||||
|
<dd>Rotates the stack so that the nth directory (counting from the left of the list shown by the <code>dirs</code> command, starting with zero) is at the top.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>-<em>n</em></strong> (a hyphen and an integer)</dt>
|
||||||
|
<dd>Rotates the stack so that the nth directory (counting from the right of the list shown by the <code>dirs</code> command, starting with zero) is at the top.
|
||||||
|
</dd>
|
||||||
|
<dt><strong><em>dir</em></strong> (the name of a directory)</dt>
|
||||||
|
<dd>Adds the directory <em>dir</em> to the directory stack at the top, making it the new current working directory as if it had been supplied as the argument to the <code>cd</code> builtin.
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<p>If the <code>pushd</code> command is successful, a <code>dirs</code> command is performed as well. If the first form is used, <code>pushd</code> returns 0 unless the <code>cd</code> to <em>dir</em> fails. With the second form, <code>pushd</code> returns 0 unless the directory stack is empty, a non-existent directory stack element is specified, or the directory change to the specified new current directory fails.</p>
|
||||||
|
<hr />
|
||||||
|
<p><strong>popd [-n] [+n] [-n]</strong></p>
|
||||||
|
<p>Removes entries from the directory stack. With no arguments, removes the top directory from the stack, and performs a cd to the new top directory. Arguments, if supplied, have the following meanings:</p>
|
||||||
|
<dl>
|
||||||
|
<dt><strong>-n</strong> (a hyphen and the letter 'n')</dt>
|
||||||
|
<dd>Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>+<em>n</em></strong> (a plus and an integer)</dt>
|
||||||
|
<dd>Removes the nth entry counting from the left of the list shown by dirs, starting with zero. For example: <code>popd +0</code> removes the first directory, <code>popd +1</code> the second.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>-<em>n</em></strong> (a hyphen and an integer)</dt>
|
||||||
|
<dd>Removes the nth entry counting from the right of the list shown by dirs, starting with zero. For example: <code>popd -0</code> removes the last directory, <code>popd -1</code> the next to last.
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<p>If the <code>popd</code> command is successful, a <code>dirs</code> command is performed as well, and the return status is 0. <code>popd</code> returns false if an invalid option is encountered, the directory stack is empty, a non-existent directory stack entry is specified, or the directory change fails.</p>
|
||||||
|
<hr />
|
||||||
|
<p><strong>dirs [-clpv] [+n] [-n]</strong></p>
|
||||||
|
<p>Without options, displays the list of currently remembered directories. The default display is on a single line with directory names separated by spaces. Directories are added to the list with the pushd command; the popd command removes entries from the list.</p>
|
||||||
|
<dl>
|
||||||
|
<dt><strong>-c</strong></dt>
|
||||||
|
<dd>Clears the directory stack by deleting all of the entries.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>-l</strong></dt>
|
||||||
|
<dd>Produces a listing using full pathnames; the default listing format uses a tilde to denote the home directory.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>-p</strong></dt>
|
||||||
|
<dd>Print the directory stack with one entry per line.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>-v</strong></dt>
|
||||||
|
<dd>Print the directory stack with one entry per line, prefixing each entry with its index in the stack.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>+<em>n</em></strong> (a plus and an integer)</dt>
|
||||||
|
<dd>Displays the nth entry counting from the left of the list shown by dirs when invoked without options, starting with zero.
|
||||||
|
</dd>
|
||||||
|
<dt><strong>-<em>n</em></strong> (a hyphen and an integer)</dt>
|
||||||
|
<dd>Displays the nth entry counting from the right of the list shown by dirs when invoked without options, starting with zero.
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<p>The return value is 0 unless an invalid option is supplied or n indexes beyond the end of the directory stack.</p>
|
||||||
|
<hr />
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
141
eps/hpr1864/hpr1864_full_shownotes.html
Executable file
@@ -0,0 +1,141 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Turning an old printer into a network printer (HPR Show 1864)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Turning an old printer into a network printer (HPR Show 1864)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#introduction">Introduction</a></li>
|
||||||
|
<li><a href="#using-a-raspberry-pi">Using a Raspberry Pi</a></li>
|
||||||
|
<li><a href="#cups">CUPS</a><ul>
|
||||||
|
<li><a href="#cups-web-interface">CUPS Web Interface</a></li>
|
||||||
|
<li><a href="#accessing-the-printer">Accessing the printer</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#sane">SANE</a><ul>
|
||||||
|
<li><a href="#accessing-the-scanner-from-mac-osx-and-windows">Accessing the scanner from Mac OSX and Windows</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="introduction">Introduction</h2>
|
||||||
|
<p>I have a USB printer I bought back in 2005 when I bought a Windows PC for the family. It's an <a href="http://h10025.www1.hp.com/ewfrf/wc/product?cc=uk&lc=en&product=303753">HP PSC 2410 PhotoSmart All-in-One printer</a>. This device is a colour inkjet printer, with a scanner, FAX and card-reading facilities. It has been left unused in a corner for many years, and I recently decided to to see if I could make use of it again, so I cleaned it up and bought some new ink cartridges for it.</p>
|
||||||
|
<p>This printer is well catered for under Linux and it is possible to access it using <a href="https://www.cups.org/" title="CUPS">CUPS</a> for the printing and <a href="http://en.wikipedia.org/wiki/Scanner_Access_Now_Easy" title="SANE">SANE</a> for scanning. I connected it to my Linux desktop for a while to prove that it was usable. However, rather than leaving it connected in this way, I wanted to turn it into a network printer that could be used by the rest of the family. My kids are mostly away at university these days but invariably need to print stuff when they pass through. I searched the Internet and found an <a href="http://www.raspberry-pi-geek.com/Archive/2013/01/Converting-the-Raspberry-Pi-to-a-wireless-print-server" title="Raspberry Pi Geek">article</a> in the <em>Raspberry Pi Geek</em> magazine which helped with this project.</p>
|
||||||
|
<h2 id="using-a-raspberry-pi">Using a Raspberry Pi</h2>
|
||||||
|
<p>I decided to use my oldest Raspberry Pi to run this printer. I have a 2 USB port Model B that I bought when these devices first came out in 2012, and I haven't used it much. It's in one of the early <a href="http://pimoroni.com/" title="Pimoroni">Pimoroni</a> <em>Pibow</em> acrylic rainbow cases.</p>
|
||||||
|
<p>I connected the Pi directly to my router with an ethernet cable. The router is on a shelf I put up in the corner of my living room and the Pi fits on there quite comfortably.</p>
|
||||||
|
<p>The printer is on a small table in the corner of the room under the router shelf. It's connected to the Pi with a USB cable. It needs a reasonable amount of space, mainly for the paper tray and the output tray at the front. It also needs room to be able to open the lid of the scanner for scanning and copying purposes.</p>
|
||||||
|
<p>The Pi is running Raspbian off a class 10 32Gb SD card. This is overkill for this function, but I already had the card. I made sure I had the latest Raspbian release on this card.</p>
|
||||||
|
<p>Because the power supply for the printer seems to consume power even when the printer is off I installed one of the <a href="http://www.amazon.co.uk/Brennenstuhl-Energy-Efficient-Control-Receivers-White/dp/B003BIFLSY/ref=pd_sim_ce_1?ie=UTF8&refRID=1KZJS5QT3ZDK1BJCN5AE" title="Remote-controlled switch">radio-controlled switches</a> I use in the house and turn it on and off with a remote control. The turning on and all of the activities of this printer are a matter of extreme fascination for my cat.</p>
|
||||||
|
<p><img src="hpr1864_printer_monitor.png" alt="Cat monitor" /><br />Picture: cat > printer</p>
|
||||||
|
<h2 id="cups">CUPS</h2>
|
||||||
|
<p>I configured the Pi while it was connected to a monitor, keyboard and mouse, enabled SSH on it and put it in its final location, running headless. I normally assign all of my local machines a fixed DHCP address on the router and add this address to <code>/etc/hosts</code> on all of my client machines. This one ended up being called <code>rpi1</code> with the address <code>192.168.0.66</code>.</p>
|
||||||
|
<p>In the headless mode I installed <a href="https://www.cups.org/" title="CUPS">CUPS</a>. On Raspbian this brings in many other packages such as HP Linux Imaging and Printing (<a href="http://hplipopensource.com/hplip-web/index.html" title="HPLIP">HPLIP</a>), <em>Scanner Access Now Easy</em> (<a href="http://en.wikipedia.org/wiki/Scanner_Access_Now_Easy" title="SANE">SANE</a>) and many filters and printer drivers.</p>
|
||||||
|
<p>Once CUPS is installed it is fairly simple to configure it either through the <code>hp-setup</code> tool or the web interface. As you might have guessed, <code>hp-setup</code> is primarily used for setting up HP printers which interface to the HPLIP software. Since I was setting up an HP printer I used the command <code>hp-setup -i</code> for this and used it to configure a print queue with the <code>hpcups</code> driver.</p>
|
||||||
|
<h3 id="cups-web-interface">CUPS Web Interface</h3>
|
||||||
|
<p>If you are doing this you might prefer to use the web interface to CUPS, especially if you are using a non-HP printer. The interface is available on port 631, so in my case I simply point a browser to my Raspberry Pi with the following URL <strong>http://192.168.0.66:631/</strong></p>
|
||||||
|
<p>In order to be able to perform administrative functions such as managing printers and print jobs, it is necessary to authenticate to CUPS and to use credentials which have the ability to perform printer administration.</p>
|
||||||
|
<p>I chose to give my account "<code>dave</code>" on the Raspberry Pi <code>lpadmin</code> rights:</p>
|
||||||
|
<pre><code>sudo usermod -a -G lpadmin dave</code></pre>
|
||||||
|
<p>The Raspberry Pi Geek magazine <a href="http://www.raspberry-pi-geek.com/Archive/2013/01/Converting-the-Raspberry-Pi-to-a-wireless-print-server" title="Raspberry Pi Geek">article</a> recommends the following steps to make the printer visible from any address:</p>
|
||||||
|
<pre><code>sudo cupsctl --remote-any
|
||||||
|
sudo /etc/init.d/cups restart</code></pre>
|
||||||
|
<p>I actually used the following to restart CUPS:</p>
|
||||||
|
<pre><code>sudo service cups restart</code></pre>
|
||||||
|
<p>Using the web interface I was able to create CUPS printer queues on the Raspberry Pi.</p>
|
||||||
|
<h3 id="accessing-the-printer">Accessing the printer</h3>
|
||||||
|
<p>The Raspberry Pi Geek magazine <a href="http://www.raspberry-pi-geek.com/Archive/2013/01/Converting-the-Raspberry-Pi-to-a-wireless-print-server" title="Raspberry Pi Geek">article</a> gives advice on setting up print clients on remote systems. I did this on my Debian Testing KDE system by invoking <em>Systems Settings</em> and selecting the <em>Printers</em> entry. My son did the equivalent on his MacBook in order to print.</p>
|
||||||
|
<p>I have not yet managed to get my Android phone or Nexus 7 tablet to print this way, though they can both print to my networked HP LaserJet printer via the free <em>HP Print Service</em> plugin. I assume this does not use a standard LPR interface. I am not prepared to pay money to make a Unix device print and I have only found non-free Android apps so far.</p>
|
||||||
|
<p>At the time of writing I have not managed to set up my daughter's Windows 8 PC to use this printer. I realised that I could set up <em>SAMBA</em> on the Raspberry Pi, but was reluctant to do so.</p>
|
||||||
|
<p>My daughter is away at university now, but I recently found some helpful advice on this subject on the <a href="https://wiki.archlinux.org/index.php/CUPS_printer_sharing#Linux_server_-_Windows_client" title="CUPS - Linux Server Windows Client">Arch Wiki</a>. In particular, the use of <a href="https://en.wikipedia.org/wiki/Internet_Printing_Protocol" title="IPP">IPP</a> (Internet Printing Protocol) seems to be the best route. Interestingly the advice is <strong>not</strong> to use SAMBA, which appeals to me!</p>
|
||||||
|
<p>It looks as if we can simply add a printer with the address:</p>
|
||||||
|
<pre><code>http://192.168.0.66:631/printers/HP_psc_2400_series</code></pre>
|
||||||
|
<h2 id="sane">SANE</h2>
|
||||||
|
<p>Using the scanner on the remote printer should be straightforward, but I encountered some problems with this.</p>
|
||||||
|
<p>According to the Raspberry Pi Geek magazine <a href="http://www.raspberry-pi-geek.com/Archive/2013/01/Converting-the-Raspberry-Pi-to-a-wireless-print-server" title="Raspberry Pi Geek">article</a> it is necessary to make the SANE daemon run all the time by editing the file <code>/etc/default/saned</code>. I ensured mine contained the following:</p>
|
||||||
|
<pre><code>RUN=yes
|
||||||
|
RUN_AS_USER=saned</code></pre>
|
||||||
|
<p>I also edited <code>/etc/sane.d/saned.conf</code> and added the line:</p>
|
||||||
|
<pre><code>192.168.0.0/24</code></pre>
|
||||||
|
<p>since all of my local network is in the range 192.168.0.0-192.168.0.255, and this allows access from everything in this range. I then started the daemon with:</p>
|
||||||
|
<pre><code>sudo service saned restart</code></pre>
|
||||||
|
<p>I then tried the following command on the Pi:</p>
|
||||||
|
<pre><code>sudo scanimage -L</code></pre>
|
||||||
|
<p>and saw the following response:</p>
|
||||||
|
<pre><code>device `hpaio:/usb/psc_2400_series?serial=MY47KM22Y36T' is a Hewlett-Packard psc_2400_series all-in-one</code></pre>
|
||||||
|
<p>Moving to my desktop system, which had CUPS installed, the same command did not find the scanner.</p>
|
||||||
|
<p>Examining <code>/var/log/daemon.log</code> on the Pi, I saw:</p>
|
||||||
|
<pre><code>Feb 21 17:14:25 rpi1 saned[3432]: check_host: access by remote host: 192.168.0.5
|
||||||
|
Feb 21 17:14:25 rpi1 saned[3432]: init: access granted to cendjm@192.168.0.5
|
||||||
|
Feb 21 17:14:28 rpi1 saned[3432]: io/hpmud/musb.c 2066: Invalid usb_open: Permission denied
|
||||||
|
Feb 21 17:14:28 rpi1 saned[3432]: quit: exiting</code></pre>
|
||||||
|
<p>It took me a little while to work out what was happening here. To cut a long story short, I found the device and looked at the permissions on it:</p>
|
||||||
|
<pre><code>root@rpi1:~# lsusb
|
||||||
|
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
|
||||||
|
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
|
||||||
|
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
|
||||||
|
Bus 001 Device 009: ID 03f0:3611 Hewlett-Packard PSC 2410 PhotoSmart
|
||||||
|
|
||||||
|
root@rpi1:~# ls -l /dev/bus/usb/001/009
|
||||||
|
crw-rw-r-T 1 root lp 189, 8 Mar 1 17:17 /dev/bus/usb/001/009</code></pre>
|
||||||
|
<p>The USB device is owned by root and the group lp. However, the user <code>saned</code> has the following groups:</p>
|
||||||
|
<pre><code>root@rpi1:~# id saned
|
||||||
|
uid=110(saned) gid=114(saned) groups=114(saned),110(scanner)</code></pre>
|
||||||
|
<p>So user <code>saned</code> cannot access the device.</p>
|
||||||
|
<p>It seemed that the simplest solution was to add <code>saned</code> to the <code>lp</code> group:</p>
|
||||||
|
<pre><code>root@rpi1:~# usermod -a -G lp saned
|
||||||
|
root@rpi1:~# id saned
|
||||||
|
uid=110(saned) gid=114(saned) groups=114(saned),7(lp),110(scanner)</code></pre>
|
||||||
|
<p>Now <code>scanimage -L</code> from the remote client returned:</p>
|
||||||
|
<pre><code>root@i7-desktop:~# scanimage -L
|
||||||
|
device `net:192.168.0.66:hpaio:/usb/psc_2400_series?serial=MY47KM22Y36T' is a Hewlett-Packard psc_2400_series all-in-one</code></pre>
|
||||||
|
<p>Now it is possible for a remote system to access the scanner through <em>GIMP</em>, <em>Xsane</em> and other SANE interfaces.</p>
|
||||||
|
<p>It is not clear why the standard CUPS installation creates user <code>saned</code> in the <code>scanner</code> group where the device is owned by the <code>lp</code> group. I have not determined if this is a problem with the <code>saned</code> user or the UDEV code that creates the device.</p>
|
||||||
|
<h3 id="accessing-the-scanner-from-mac-osx-and-windows">Accessing the scanner from Mac OSX and Windows</h3>
|
||||||
|
<p>I have not yet managed to test these options. When a scan is required I run it on a Linux system and email it or share it via DropBox.</p>
|
||||||
|
<p>This is obviously an area for future development!</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ol>
|
||||||
|
<li>HP PSC 2410 PhotoSmart All-in-One printer: <a href="http://h10025.www1.hp.com/ewfrf/wc/product?cc=uk&lc=en&product=303753" class="uri">http://h10025.www1.hp.com/ewfrf/wc/product?cc=uk&lc=en&product=303753</a></li>
|
||||||
|
<li><code>CUPS.org</code> main web site: <a href="https://www.cups.org/" class="uri">https://www.cups.org/</a></li>
|
||||||
|
<li>CUPS Wikipedia entry: <a href="http://en.wikipedia.org/wiki/CUPS" class="uri">http://en.wikipedia.org/wiki/CUPS</a></li>
|
||||||
|
<li>HP Linux Imaging and Printing (HPLIP): <a href="http://hplipopensource.com/hplip-web/index.html" class="uri">http://hplipopensource.com/hplip-web/index.html</a></li>
|
||||||
|
<li><em>Scanner Access Now Easy</em> (SANE): <a href="http://en.wikipedia.org/wiki/Scanner_Access_Now_Easy" class="uri">http://en.wikipedia.org/wiki/Scanner_Access_Now_Easy</a></li>
|
||||||
|
<li>"<em>Converting the Raspberry Pi to a wireless print server</em>" from the Raspberry Pi Geek magazine: <a href="http://www.raspberry-pi-geek.com/Archive/2013/01/Converting-the-Raspberry-Pi-to-a-wireless-print-server" class="uri">http://www.raspberry-pi-geek.com/Archive/2013/01/Converting-the-Raspberry-Pi-to-a-wireless-print-server</a></li>
|
||||||
|
<li>Linux Foundation <em>OpenPrinting</em> work group: <a href="http://www.linuxfoundation.org/collaborate/workgroups/openprinting/" class="uri">http://www.linuxfoundation.org/collaborate/workgroups/openprinting/</a>
|
||||||
|
<ul>
|
||||||
|
<li>HP PSC 2400 series details: <a href="http://www.openprinting.org/printer/HP/HP-PSC_2400" class="uri">http://www.openprinting.org/printer/HP/HP-PSC_2400</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Arch Wiki on CUPS - Linux Server Windows Client: <a href="https://wiki.archlinux.org/index.php/CUPS_printer_sharing#Linux_server_-_Windows_client" class="uri">https://wiki.archlinux.org/index.php/CUPS_printer_sharing#Linux_server_-_Windows_client</a></li>
|
||||||
|
<li>Internet Printing Protocol (IPP): <a href="https://en.wikipedia.org/wiki/Internet_Printing_Protocol" class="uri">https://en.wikipedia.org/wiki/Internet_Printing_Protocol</a></li>
|
||||||
|
</ol>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1864/hpr1864_printer_monitor.png
Executable file
|
After Width: | Height: | Size: 184 KiB |
161
eps/hpr1884/hpr1884_full_shownotes.html
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Some more Bash tips (HPR Show 1884)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Some more Bash tips (HPR Show 1884)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#introduction">Introduction</a></li>
|
||||||
|
<li><a href="#brace-expansion">Brace expansion</a><ul>
|
||||||
|
<li><a href="#comma-separated-strings">Comma separated strings</a></li>
|
||||||
|
<li><a href="#sequence-expressions">Sequence expressions</a></li>
|
||||||
|
<li><a href="#uses-for-brace-expansion">Uses for brace expansion</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#the-seq-command">The <em>seq</em> command</a></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
<li><a href="#manual-page-extracts">Manual Page Extracts</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="introduction">Introduction</h2>
|
||||||
|
<p>We looked at <em>Parameter Expansion</em> back in HPR episode <a href="https://hackerpublicradio.org/eps/hpr1648" title="Bash parameter manipulation">1648</a> where we saw how Bash variables could be used, checked and edited. There are other sorts of expansions within Bash, and we'll look at one called "<em>Brace Expansion</em>" in this episode, which follows on from episode <a href="https://hackerpublicradio.org/eps/hpr1843" title="Some Bash tips">1843</a> "<em>Some Bash tips</em>".</p>
|
||||||
|
<p>I have included some extracts from the <strong>Bash</strong> manual page for reference.</p>
|
||||||
|
<h2 id="brace-expansion">Brace expansion</h2>
|
||||||
|
<p>In brace expansion an expression enclosed in braces is expanded. The expression can be a list of comma separated strings or a sequence expression.</p>
|
||||||
|
<h3 id="comma-separated-strings">Comma separated strings</h3>
|
||||||
|
<p>This can just be a series of letters such as:</p>
|
||||||
|
<pre><code>echo c{a,e,i,o,u}t
|
||||||
|
-> cat cet cit cot cut</code></pre>
|
||||||
|
<p>Note: The line beginning '<strong>-></strong>' is what will be generated by the above statement. I will be using this method of signifying output through these notes.</p>
|
||||||
|
<p>Here you see that each of the letters is inserted in between the letters <em>c</em> and <em>t</em> to make the list shown. This does not work inside quotes:</p>
|
||||||
|
<pre><code>echo "c{a,e,i,o,u}t"
|
||||||
|
-> c{a,e,i,o,u}t</code></pre>
|
||||||
|
<p>The comma separated strings can be longer than a single character:</p>
|
||||||
|
<pre><code>echo "|"{cow,pig,sheep}"|"
|
||||||
|
-> |cow| |pig| |sheep|</code></pre>
|
||||||
|
<p>Note that, since the "|" character is special within the Bash shell it has to be quoted to remove its special nature.</p>
|
||||||
|
<h3 id="sequence-expressions">Sequence expressions</h3>
|
||||||
|
<p>The sequence expression form consists of a range of values written like this:</p>
|
||||||
|
<pre><code>{x..y}</code></pre>
|
||||||
|
<p>For example, to get the numbers 0 to 5:</p>
|
||||||
|
<pre><code>echo {0..5}
|
||||||
|
-> 0 1 2 3 4 5</code></pre>
|
||||||
|
<p>There is an optional third part:</p>
|
||||||
|
<pre><code>{x..y..incr}</code></pre>
|
||||||
|
<p>The '<em>incr</em>' part is a positive or negative integer indicating the size of the steps to be used between <em>x</em> and <em>y</em>. For example:</p>
|
||||||
|
<pre><code>echo {0..100..10}
|
||||||
|
-> 0 10 20 30 40 50 60 70 80 90 100
|
||||||
|
echo {100..0..-10}
|
||||||
|
-> 100 90 80 70 60 50 40 30 20 10 0</code></pre>
|
||||||
|
<p>If either <em>x</em> or <em>y</em> have leading zeroes these are maintained to ensure the resulting numbers are of the same width:</p>
|
||||||
|
<pre><code>echo {000..100..10}
|
||||||
|
-> 000 010 020 030 040 050 060 070 080 090 100</code></pre>
|
||||||
|
<p>The <em>x</em> and <em>y</em> parts can also be single characters with an optional (numeric) increment such as:</p>
|
||||||
|
<pre><code>echo {a..j}
|
||||||
|
-> a b c d e f g h i j
|
||||||
|
echo {a..j..2}
|
||||||
|
-> a c e g i
|
||||||
|
echo {z..t..-2}
|
||||||
|
-> z x v t</code></pre>
|
||||||
|
<h3 id="uses-for-brace-expansion">Uses for brace expansion</h3>
|
||||||
|
<p>The two forms are often used to generate lists of filenames for use in commands which take multiple arguments. For example, if a directory contains image files interspersed with other files, which you wish to examine with the <code>ls</code> command you might type:</p>
|
||||||
|
<pre><code>ls -l ~/Pictures/*.{jpg,png}</code></pre>
|
||||||
|
<p>You might want to <code>grep</code> a set of log files for a particular string, restricting yourself to a range of days:</p>
|
||||||
|
<pre><code>grep "^Reset" -A7 logs/*201509{01..13}*.log</code></pre>
|
||||||
|
<p>It's a useful way to generate sequences in a loop:</p>
|
||||||
|
<pre><code>for i in {0..10}; do echo $i; done</code></pre>
|
||||||
|
<p>I was recently experimenting with the <code>printf</code> command and its <code>'%b'</code> argument, and I used the following statements to show what it did:</p>
|
||||||
|
<pre><code>echo -n ">"; printf '%b' "\x"{20..29}; echo "<"
|
||||||
|
> !"#$%&'()<</code></pre>
|
||||||
|
<p>To explain, <code>'%b'</code> expects its argument to be a string with values such as '20' meaning hex 20, which it turns into the corresponding character. You can see that hex 20 is a space, hex 21 and exclamation mark and so forth.</p>
|
||||||
|
<h2 id="the-seq-command">The <em>seq</em> command</h2>
|
||||||
|
<p>There is a command <code>seq</code> which can do a lot of what the Bash brace expansion sequence expressions can do, and a few things more. A copy of the manual page is included below.</p>
|
||||||
|
<p>The <code>seq</code> command treats its values as floating point values, so it is possible to do things like:</p>
|
||||||
|
<pre><code>seq -f '%0.1f' -s' ' 0 2.5 10
|
||||||
|
-> 0.0 2.5 5.0 7.5 10.0</code></pre>
|
||||||
|
<p>It cannot produce a sequence of letters however.</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>HPR episode 1648 "<em>Bash parameter manipulation</em>": <a href="https://hackerpublicradio.org/eps/hpr1648" class="uri">https://hackerpublicradio.org/eps/hpr1648</a></li>
|
||||||
|
<li>HPR episode 1843 "<em>Some Bash tips</em>": <a href="https://hackerpublicradio.org/eps/hpr1843" class="uri">https://hackerpublicradio.org/eps/hpr1843</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<h1 id="manual-page-extracts">Manual Page Extracts</h1>
|
||||||
|
<p><strong>Brace Expansion</strong></p>
|
||||||
|
<p><em>Brace expansion</em> is a mechanism by which arbitrary strings may be generated. This mechanism is similar to <em>pathname expansion</em>, but the filenames generated need not exist. Patterns to be brace expanded take the form of an optional <em>preamble</em>, followed by either a series of comma-separated strings or a sequence expression between a pair of braces, followed by an optional <em>postscript</em>. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.</p>
|
||||||
|
<p>Brace expansions may be nested. The results of each expanded string are not sorted; left to right order is preserved. For example, '<code>a{d,c,b}e</code>' expands into '<code>ade ace abe</code>'.</p>
|
||||||
|
<p>A sequence expression takes the form <strong>{x..y[..incr]}</strong>, where <em>x</em> and <em>y</em> are either integers or single characters, and <em>incr</em>, an optional increment, is an integer. When integers are supplied, the expression expands to each number between <em>x</em> and <em>y</em>, inclusive. Supplied integers may be prefixed with <em>0</em> to force each term to have the same width. When either <em>x</em> or <em>y</em> begins with a zero, the shell attempts to force all generated terms to contain the same number of digits, zero-padding where necessary. When characters are supplied, the expression expands to each character lexicographically between <em>x</em> and <em>y</em>, inclusive, using the default C locale. Note that both <em>x</em> and <em>y</em> must be of the same type. When the increment is supplied, it is used as the difference between each term. The default increment is 1 or -1 as appropriate.</p>
|
||||||
|
<p>Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.</p>
|
||||||
|
<p>A correctly-formed brace expansion must contain unquoted opening and closing braces, and at least one unquoted comma or a valid sequence expression. Any incorrectly formed brace expansion is left unchanged. A <strong>{</strong> or <strong>,</strong> may be quoted with a backslash to prevent its being considered part of a brace expression. To avoid conflicts with parameter expansion, the string <strong>${</strong> is not considered eligible for brace expansion.</p>
|
||||||
|
<p>This construct is typically used as shorthand when the common prefix of the strings to be generated is longer than in the above example:</p>
|
||||||
|
<pre><code>mkdir /usr/local/src/bash/{old,new,dist,bugs}</code></pre>
|
||||||
|
<p>or chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}</p>
|
||||||
|
<p>Brace expansion introduces a slight incompatibility with historical versions of <strong>sh</strong>. <strong>sh</strong> does not treat opening or closing braces specially when they appear as part of a word, and preserves them in the output. Bash removes braces from words as a consequence of brace expansion. For example, a word entered to <strong>sh</strong> as <em>file{1,2}</em> appears identically in the output. The same word is output as file1 file2 after expansion by <strong>bash</strong>. If strict compatibility with sh is desired, start <strong>bash</strong> with the <strong>+B</strong> option or disable brace expansion with the <strong>+B</strong> option to the set command (see <strong>SHELL BUILTIN COMMANDS</strong> below).</p>
|
||||||
|
<hr />
|
||||||
|
<p><strong>NAME</strong></p>
|
||||||
|
<pre><code>seq - print a sequence of numbers</code></pre>
|
||||||
|
<p><strong>SYNOPSIS</strong></p>
|
||||||
|
<pre><code>seq [OPTION]... LAST
|
||||||
|
seq [OPTION]... FIRST LAST
|
||||||
|
seq [OPTION]... FIRST INCREMENT LAST</code></pre>
|
||||||
|
<p><strong>DESCRIPTION</strong></p>
|
||||||
|
<pre><code>Print numbers from FIRST to LAST, in steps of INCREMENT.
|
||||||
|
|
||||||
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
|
||||||
|
-f, --format=FORMAT
|
||||||
|
use printf style floating-point FORMAT
|
||||||
|
|
||||||
|
-s, --separator=STRING
|
||||||
|
use STRING to separate numbers (default: \n)
|
||||||
|
|
||||||
|
-w, --equal-width
|
||||||
|
equalize width by padding with leading zeroes
|
||||||
|
|
||||||
|
--help display this help and exit
|
||||||
|
|
||||||
|
--version
|
||||||
|
output version information and exit
|
||||||
|
|
||||||
|
If FIRST or INCREMENT is omitted, it defaults to 1. That is, an omitted
|
||||||
|
INCREMENT defaults to 1 even when LAST is smaller than FIRST. The
|
||||||
|
sequence of numbers ends when the sum of the current number and INCREMENT
|
||||||
|
would become greater than LAST. FIRST, INCREMENT, and LAST are
|
||||||
|
interpreted as floating point values. INCREMENT is usually positive if
|
||||||
|
FIRST is smaller than LAST, and INCREMENT is usu‐ ally negative if FIRST
|
||||||
|
is greater than LAST. FORMAT must be suitable for printing one argument
|
||||||
|
of type 'double'; it defaults to %.PRECf if FIRST, INCREMENT, and LAST are
|
||||||
|
all fixed point decimal num‐ bers with maximum precision PREC, and to %g
|
||||||
|
otherwise.</code></pre>
|
||||||
|
<p><strong>AUTHOR</strong></p>
|
||||||
|
<pre><code>Written by Ulrich Drepper.</code></pre>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
240
eps/hpr1903/hpr1903_full_shownotes.html
Executable file
@@ -0,0 +1,240 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>Some further Bash tips (HPR Show 1903)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Some further Bash tips (HPR Show 1903)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#expansion">Expansion</a><ul>
|
||||||
|
<li><a href="#tilde-expansion">Tilde expansion</a><ul>
|
||||||
|
<li><a href="#tilde-on-its-own">Tilde on its own</a></li>
|
||||||
|
<li><a href="#tilde-and-a-login-name">Tilde and a login name</a></li>
|
||||||
|
<li><a href="#tilde-with-a-plus-sign">Tilde with a plus sign</a></li>
|
||||||
|
<li><a href="#tilde-and-a-minus-sign">Tilde and a minus sign</a></li>
|
||||||
|
<li><a href="#tilde-and-the-directory-stack">Tilde and the directory stack</a></li>
|
||||||
|
<li><a href="#tilde-expansion-in-variables">Tilde expansion in variables</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#command-substitution">Command Substitution</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
<li><a href="#manual-page-extracts">Manual Page Extracts</a><ul>
|
||||||
|
<li><a href="#expansion-1">EXPANSION</a><ul>
|
||||||
|
<li><a href="#brace-expansion">Brace Expansion</a></li>
|
||||||
|
<li><a href="#tilde-expansion-1">Tilde Expansion</a></li>
|
||||||
|
<li><a href="#parameter-expansion">Parameter Expansion</a></li>
|
||||||
|
<li><a href="#command-substitution-1">Command Substitution</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="expansion">Expansion</h2>
|
||||||
|
<p>There are seven types of expansion applied to the command line in the following order:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Brace expansion (we looked at this subject in the last episode <a href="http://hackerpublicradio.org/eps/hpr1884" title="Some more Bash tips">1884</a>)</li>
|
||||||
|
<li>Tilde expansion</li>
|
||||||
|
<li>Parameter and variable expansion (this was covered in episode <a href="http://hackerpublicradio.org/eps/hpr1648" title="Bash parameter manipulation">1648</a>)</li>
|
||||||
|
<li>Command substitution</li>
|
||||||
|
<li>Arithmetic expansion</li>
|
||||||
|
<li>Word splitting</li>
|
||||||
|
<li>Pathname expansion</li>
|
||||||
|
</ul>
|
||||||
|
<p>We will look at some more of these in this episode but since there is a lot to cover, we'll continue in a later episode.</p>
|
||||||
|
<h3 id="tilde-expansion">Tilde expansion</h3>
|
||||||
|
<p>This is a convenient way of referring to a home directory in a file path, though there are other less well-known uses which we will also examine.</p>
|
||||||
|
<h4 id="tilde-on-its-own">Tilde on its own</h4>
|
||||||
|
<p>Consider the following example. Imagine you are in the directory <code>Documents</code> and you want to look at your <code>.bashrc</code> file. Here are some ways of doing this:</p>
|
||||||
|
<pre><code>cd Documents
|
||||||
|
less ../.bashrc
|
||||||
|
less $HOME/.bashrc
|
||||||
|
less ~/.bashrc</code></pre>
|
||||||
|
<ul>
|
||||||
|
<li>The first method uses <strong>..</strong> to refer to the directory above the current one.</li>
|
||||||
|
<li>The second uses the variable <code>HOME</code>, which is usually created for you when you login, and points to your home directory.</li>
|
||||||
|
<li>The third method uses a plain tilde (<strong>~</strong>) which means the home directory of the current user.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Actually the tilde in this example uses the contents of the <code>HOME</code> variable, just like the example above it. If you happened to change this variable for some reason the changed version would be used. If there is no <code>HOME</code> variable then the defined home directory of the current user will be looked up.</p>
|
||||||
|
<p>Note: The line beginning '<strong>-></strong>' is what will be generated by the following statements. I will be using this method of signifying output throughout these notes (unless it's confusing).</p>
|
||||||
|
<pre><code>echo ~
|
||||||
|
-> /home/hprdemo
|
||||||
|
|
||||||
|
cd Documents
|
||||||
|
HOME=$PWD
|
||||||
|
echo ~
|
||||||
|
-> /home/hprdemo/Documents</code></pre>
|
||||||
|
<p><strong>Warning</strong> changing <code>HOME</code> can lead to great confusion, so it's not recommended. For example, after such a change the <code>cd</code> command without any argument moves to wherever <code>HOME</code> points to rather than to the expected home directory. This is a demonstration, <em>not</em> a recommendation!</p>
|
||||||
|
<h4 id="tilde-and-a-login-name">Tilde and a login name</h4>
|
||||||
|
<p>If the tilde is followed by a login name (username) then it refers to the home directory of that login name:</p>
|
||||||
|
<pre><code>echo ~hprdemo
|
||||||
|
-> /home/hprdemo
|
||||||
|
echo ~postgres
|
||||||
|
-> /var/lib/postgresql</code></pre>
|
||||||
|
<p>This is useful for example in multi-user environments where you want to copy files to or from someone else's directory - assuming the permissions have been set to allow this of course.</p>
|
||||||
|
<p>By the way, if you have changed the <code>HOME</code> variable it can be reset either by logging out and back in again or with the <code>~login_name</code> form as in the following:</p>
|
||||||
|
<pre><code>HOME=~hprdemo
|
||||||
|
echo ~
|
||||||
|
-> /home/hprdemo</code></pre>
|
||||||
|
<p>Like many instances in Bash, the login name after the tilde can be completed by pressing the <em>Tab</em> key. If you happen to work in an environment with many login names, then take care when doing this since it might require a search of the entire name space. I used to work at a University with up to 50,000 login names, and pressing <em>Tab</em> inappropriately could result in a big search and a very long delay!</p>
|
||||||
|
<h4 id="tilde-with-a-plus-sign">Tilde with a plus sign</h4>
|
||||||
|
<p>There are other forms of tilde expansion. First, <strong>~+</strong> uses the value of the <code>PWD</code> variable. This variable is used by Bash to track the directory you are currently in.</p>
|
||||||
|
<pre><code>cd Documents
|
||||||
|
echo ~+
|
||||||
|
-> /home/hprdemo/Documents</code></pre>
|
||||||
|
<h4 id="tilde-and-a-minus-sign">Tilde and a minus sign</h4>
|
||||||
|
<p>There is another variable, <code>OLDPWD</code> that is used to hold the previous contents of <code>PWD</code>. This can be accessed with <strong>~-</strong>.</p>
|
||||||
|
<pre><code>cd Documents
|
||||||
|
echo ~-
|
||||||
|
-> /home/hprdemo
|
||||||
|
echo ~+
|
||||||
|
-> /home/hprdemo/Documents</code></pre>
|
||||||
|
<h4 id="tilde-and-the-directory-stack">Tilde and the directory stack</h4>
|
||||||
|
<p>There is one more way in which tilde expansion can be used in Bash. This links to the directory stack that we looked at in show <a href="http://hackerpublicradio.org/eps/hpr1843" title="Some Bash tips">1843</a>. In that show we saw the <code>pushd</code> and <code>popd</code> commands for manipulating the stack by adding and removing directories. We also saw the <code>dirs</code> command for showing the contents of the stack.</p>
|
||||||
|
<p>Using <strong>~</strong> followed by a <strong>+</strong> or a <strong>-</strong> and a number references a directory on the stack. Using <code>dirs -v</code> we can see the stack with numbered entries (we're not using the <strong>-></strong> here as it might be confusing):</p>
|
||||||
|
<pre><code>dirs -v
|
||||||
|
0 ~/Documents
|
||||||
|
1 ~</code></pre>
|
||||||
|
<p>In such a case the tilde sequence <strong>~1</strong> (or <strong>~+1</strong>) references the stack element numbered <strong>1</strong> above:</p>
|
||||||
|
<pre><code>echo ~1
|
||||||
|
-> /home/hprdemo</code></pre>
|
||||||
|
<p>Note how the tilde stored in the stack representation is expanded in this example.</p>
|
||||||
|
<p>The directory returned is the same as that reported by <code>dirs -l +1</code> where the <code>-l</code> option requests the full form be displayed.</p>
|
||||||
|
<p>As discussed in show <a href="http://hackerpublicradio.org/eps/hpr1843" title="Some Bash tips">1843</a> we can also reference stack elements in reverse order. So the tilde expression <strong>~-1</strong> in the above scenario will return the second element counting from the bottom:</p>
|
||||||
|
<pre><code>echo ~-1
|
||||||
|
-> /home/hprdemo/Documents</code></pre>
|
||||||
|
<p>The directory returned is the same as that reported by <code>dirs -l -1</code>.</p>
|
||||||
|
<h4 id="tilde-expansion-in-variables">Tilde expansion in variables</h4>
|
||||||
|
<p>Normally the tilde forms we have looked at would be used in file system paths when referring to files or directories. It is also possible to assign their values to variables, such as:</p>
|
||||||
|
<pre><code>docs=~/Documents
|
||||||
|
echo $docs
|
||||||
|
-> /home/hprdemo/Documents</code></pre>
|
||||||
|
<p>Bash provides special variables (and other software might need its own such variables) which contain lists of paths separated by colons (<strong>:</strong>). For example, the <code>PATH</code> variable, which contains paths used when searching for commands:</p>
|
||||||
|
<pre><code>echo $PATH
|
||||||
|
-> /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games</code></pre>
|
||||||
|
<p>Bash allows the tilde expansion formats we have seen to be included in such lists:</p>
|
||||||
|
<pre><code>PATH+=:~/bin
|
||||||
|
echo $PATH
|
||||||
|
-> /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/hprdemo/bin</code></pre>
|
||||||
|
<p>Notice how the addition to the <code>PATH</code> variable was not enclosed in quotes. If it had been then the tilde expansion would not have taken place.</p>
|
||||||
|
<h3 id="command-substitution">Command Substitution</h3>
|
||||||
|
<p>Commands often write output. Unless told otherwise they write this output to a channel known as <strong>standard output</strong> (<em>STDOUT</em>). It is possible to capture this output channel and use it in many contexts.</p>
|
||||||
|
<p>Take for example the <code>date</code> command. This reports a date and, optionally a time, in various formats. To get today's date in the ISO8601 format (<em>the only sane format, which everyone should adopt</em>) the following command could be used:</p>
|
||||||
|
<pre><code>date +%Y-%m-%d
|
||||||
|
-> 2015-11-04</code></pre>
|
||||||
|
<p>This output could be captured in a variable using <em>command substitution</em> as follows:</p>
|
||||||
|
<pre><code>today=$(date +%Y-%m-%d)
|
||||||
|
echo $today
|
||||||
|
-> 2015-11-04</code></pre>
|
||||||
|
<p>The format <code>$(command)</code> for command substitution is the recommended one to use. There is an older form which uses <em>backquotes</em> around the command. The above example could be rewritten as:</p>
|
||||||
|
<pre><code>today=`date +%Y-%m-%d`
|
||||||
|
echo $today
|
||||||
|
-> 2015-11-04</code></pre>
|
||||||
|
<p>We will discuss only the <code>$(command)</code> form in these notes. See the manual page extract for details of the other format.</p>
|
||||||
|
<p>The text returned by the command is processed to remove newlines. The following example shows the <code>date</code> command being used to generate multi-line output (the sequence <strong>%n</strong> generates a newline in output from <code>date</code>, and we're not using the <strong>-></strong> here to avoid confusion):</p>
|
||||||
|
<pre><code>date +"Today's date is%n%Y-%m-%d"
|
||||||
|
Today's date is
|
||||||
|
2015-11-04</code></pre>
|
||||||
|
<p>(Note that the argument to <code>date</code> is quoted because it contains spaces).</p>
|
||||||
|
<p>Using this in command substitution we get a different result:</p>
|
||||||
|
<pre><code>today=$(date +"Today's date is%n%Y-%m-%d%n")
|
||||||
|
echo $today
|
||||||
|
-> Today's date is 2015-11-04</code></pre>
|
||||||
|
<p>The embedded newline has been removed and replaced by a space.</p>
|
||||||
|
<p>As a final example, consider the following. A file <code>words</code> exists with one word per line. We want to construct a Bash loop which processes this file. To keep things simple we'll just <code>echo</code> each word followed by its length:</p>
|
||||||
|
<pre><code>for w in $(cat words)
|
||||||
|
do
|
||||||
|
echo "$w (${#w})"
|
||||||
|
done</code></pre>
|
||||||
|
<p>The <code>for</code> loop is simply given a list of words from the file by virtue of the command substitution <code>$(cat words)</code>, which it then places one at a time into the variable <code>w</code>. We use the construct <code>${#w}</code> to determine the length as discussed in show <a href="http://hackerpublicradio.org/eps/hpr1648" title="Bash parameter manipulation">1648</a>.</p>
|
||||||
|
<p>Some typical output might be:</p>
|
||||||
|
<pre><code>bulkier (7)
|
||||||
|
laxness (7)
|
||||||
|
house (5)
|
||||||
|
overshoe (8)</code></pre>
|
||||||
|
<p>There is an alternative (and faster) way of doing this without using <code>cat</code>:</p>
|
||||||
|
<pre><code>for w in $(< words)
|
||||||
|
do
|
||||||
|
echo "$w (${#w})"
|
||||||
|
done</code></pre>
|
||||||
|
<p>This is a real example; I always test the commands in my notes to check I have not made any glaring mistakes. You might be interested to know how I generated the file of words:</p>
|
||||||
|
<pre><code>for i in {1..10}
|
||||||
|
do
|
||||||
|
w=$(shuf -n1 /usr/share/dict/words)
|
||||||
|
w=${w%[^a-zA-Z]*}
|
||||||
|
echo $w
|
||||||
|
done > words</code></pre>
|
||||||
|
<p>The loop uses <em>brace expansion</em> as discussed in show <a href="http://hackerpublicradio.org/eps/hpr1884" title="Some more Bash tips">1884</a>; it iterates 10 times. The <code>shuf</code> command is used to extract one line (word) at random from the system dictionary. Because many of these words have possessive forms, I wanted to strip the apostrophe and anything beyond it and I did that with an instance of <em>Remove matching suffix pattern</em> as discussed in show <a href="http://hackerpublicradio.org/eps/hpr1648" title="Bash parameter manipulation">1648</a>. It removes any suffix consisting of a non-alphabetic character followed by others.</p>
|
||||||
|
<p>The resulting word is simply echoed.</p>
|
||||||
|
<p>The entire loop redirects its output (a list of 10 words) into the file <code>words</code>. We might be visiting the subject of redirection in a later show in this (sub-)series.</p>
|
||||||
|
<p>Since <code>shuf</code> can return multiple random words at a time, and since the removal of extraneous characters could have been done in the <code>echo</code>, this example could also have been written as:</p>
|
||||||
|
<pre><code>for w in $(shuf -n10 /usr/share/dict/words)
|
||||||
|
do
|
||||||
|
echo ${w%[^a-zA-Z]*}
|
||||||
|
done > words</code></pre>
|
||||||
|
<p>I tend to write short Bash loops of this sort on one line:</p>
|
||||||
|
<pre><code>for w in $(shuf -n10 /usr/share/dict/words); do echo ${w%[^a-zA-Z]*}; done > words</code></pre>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Bash Reference Manual:
|
||||||
|
<ul>
|
||||||
|
<li>Tilde Expansion: <a href="https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html" class="uri">https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html</a></li>
|
||||||
|
<li>Command Substitution: <a href="https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html" class="uri">https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li>HPR episode 1648 "<em>Bash parameter manipulation</em>": <a href="http://hackerpublicradio.org/eps/hpr1648" class="uri">http://hackerpublicradio.org/eps/hpr1648</a></li>
|
||||||
|
<li>HPR episode 1843 "<em>Some Bash tips</em>": <a href="http://hackerpublicradio.org/eps/hpr1843" class="uri">http://hackerpublicradio.org/eps/hpr1843</a></li>
|
||||||
|
<li>HPR episode 1884 "<em>Some more Bash tips</em>": <a href="http://hackerpublicradio.org/eps/hpr1884" class="uri">http://hackerpublicradio.org/eps/hpr1884</a></li>
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<h1 id="manual-page-extracts">Manual Page Extracts</h1>
|
||||||
|
<h2 id="expansion-1">EXPANSION</h2>
|
||||||
|
<p>Expansion is performed on the command line after it has been split into words. There are seven kinds of expansion performed: <em>brace expansion</em>, <em>tilde expansion</em>, <em>parameter and variable expansion</em>, <em>command substitution</em>, <em>arithmetic expansion</em>, <em>word splitting</em>, and <em>pathname expansion</em>.</p>
|
||||||
|
<p>The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.</p>
|
||||||
|
<p>On systems that can support it, there is an additional expansion available: <em>process substitution</em>. This is performed at the same time as tilde, parameter, variable, and arithmetic expansion and command substitution.</p>
|
||||||
|
<p>Only brace expansion, word splitting, and pathname expansion can change the number of words of the expansion; other expansions expand a single word to a single word. The only exceptions to this are the expansions of "<strong>$@</strong>" and "<strong>${name[@]}</strong>" as explained above (see <strong>PARAMETERS</strong>).</p>
|
||||||
|
<h3 id="brace-expansion">Brace Expansion</h3>
|
||||||
|
<p>See the notes for HPR show <a href="http://hackerpublicradio.org/eps/hpr1884" title="Some more Bash tips">1884</a>.</p>
|
||||||
|
<h3 id="tilde-expansion-1">Tilde Expansion</h3>
|
||||||
|
<p>If a word begins with an unquoted tilde character (<strong><code>~</code></strong>), all of the characters preceding the first unquoted slash (or all characters, if there is no unquoted slash) are considered a <em>tilde-prefix</em>. If none of the characters in the tilde-prefix are quoted, the characters in the tilde-prefix following the tilde are treated as a possible login name. If this login name is the null string, the tilde is replaced with the value of the shell parameter HOME. If HOME is unset, the home directory of the user executing the shell is substituted instead. Otherwise, the tilde-prefix is replaced with the home directory associated with the specified login name.</p>
|
||||||
|
<p>If the tilde-prefix is a <code>~+</code>, the value of the shell variable <strong>PWD</strong> replaces the tilde-prefix. If the tilde-prefix is a <code>~-</code>, the value of the shell variable <strong>OLDPWD</strong>, if it is set, is substituted. If the characters following the tilde in the tilde-prefix consist of a number N, optionally prefixed by a <code>+' or a</code>-', the tilde-prefix is replaced with the corresponding element from the directory stack, as it would be displayed by the dirs builtin invoked with the tilde-prefix as an argument. If the characters following the tilde in the tilde-prefix consist of a number without a leading <code>+</code> or <code>-</code>, <code>+</code> is assumed.</p>
|
||||||
|
<p>If the login name is invalid, or the tilde expansion fails, the word is unchanged.</p>
|
||||||
|
<p>Each variable assignment is checked for unquoted tilde-prefixes immediately following a : or the first =. In these cases, tilde expansion is also performed. Consequently, one may use filenames with tildes in assignments to <strong>PATH</strong>, <strong>MAILPATH</strong>, and <strong>CDPATH</strong>, and the shell assigns the expanded value.</p>
|
||||||
|
<h3 id="parameter-expansion">Parameter Expansion</h3>
|
||||||
|
<p>See the notes for HPR show <a href="http://hackerpublicradio.org/eps/hpr1648" title="Bash parameter manipulation">1648</a>.</p>
|
||||||
|
<h3 id="command-substitution-1">Command Substitution</h3>
|
||||||
|
<p>Command substitution allows the output of a command to replace the command name. There are two forms:</p>
|
||||||
|
<pre><code> $(command)
|
||||||
|
or
|
||||||
|
`command`</code></pre>
|
||||||
|
<p>Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during word splitting. The command substitution <code>$(cat file)</code> can be replaced by the equivalent but faster <code>$(< file)</code>.</p>
|
||||||
|
<p>When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by <strong>$</strong>, <strong>`</strong>, or <strong>\</strong>. The first backquote not preceded by a backslash terminates the command substitution. When using the <code>$(command)</code> form, all characters between the parentheses make up the command; none are treated specially.</p>
|
||||||
|
<p>Command substitutions may be nested. To nest when using the backquoted form, escape the inner backquotes with backslashes.</p>
|
||||||
|
<p>If the substitution appears within double quotes, word splitting and pathname expansion are not performed on the results.</p>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
172
eps/hpr1938/hpr1938_full_shownotes.html
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="generator" content="pandoc">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
||||||
|
<meta name="author" content="Dave Morriss">
|
||||||
|
<title>How I prepare HPR shows (HPR Show 1938)</title>
|
||||||
|
<style type="text/css">code{white-space: pre;}</style>
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="home">
|
||||||
|
<div id="container" class="shadow">
|
||||||
|
<header>
|
||||||
|
<h1 class="title">How I prepare HPR shows (HPR Show 1938)</h1>
|
||||||
|
<h2 class="author">Dave Morriss</h2>
|
||||||
|
<hr/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="maincontent">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>Table of Contents</h1>
|
||||||
|
<nav id="TOC">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#introduction">Introduction</a></li>
|
||||||
|
<li><a href="#overview">Overview</a></li>
|
||||||
|
<li><a href="#my-work-flow">My Work-flow</a></li>
|
||||||
|
<li><a href="#features-of-the-hpr_talk-script">Features of the <code>hpr_talk</code> script</a><ul>
|
||||||
|
<li><a href="#install-the-script">Install the script</a></li>
|
||||||
|
<li><a href="#create-a-new-show">Create a new show</a></li>
|
||||||
|
<li><a href="#change-the-configuration-of-a-show">Change the configuration of a show</a></li>
|
||||||
|
<li><a href="#advanced-configuration">Advanced configuration</a></li>
|
||||||
|
<li><a href="#build-the-notes">Build the notes</a></li>
|
||||||
|
<li><a href="#register-the-files-youre-going-to-upload">Register the files you're going to upload</a></li>
|
||||||
|
<li><a href="#release-the-show">Release the show</a></li>
|
||||||
|
<li><a href="#report-the-status-of-a-show-or-all-pending-shows">Report the status of a show or all pending shows</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#example-use">Example use</a></li>
|
||||||
|
<li><a href="#show-notes">Show Notes</a><ul>
|
||||||
|
<li><a href="#the-notes-are-really-a-template">The notes are really a template</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#conclusion">Conclusion</a></li>
|
||||||
|
<li><a href="#links">Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<h2 id="introduction">Introduction</h2>
|
||||||
|
<p>I have been contributing shows to Hacker Public Radio since 2012. In those far off days (!) we sent everything in via FTP, and had to name the files with a combination of our host id, our name, the slot number and the title. The show notes had to contain a chunk of metadata in a defined format to signal all of the various attributes of the show. I found myself making numerous mistakes with this naming and metadata formatting and so started designing and writing some tools to protect myself from my own errors.</p>
|
||||||
|
<p>I started developing a Bash script in mid-2013 which I called <code>hpr_talk</code>. I used Bash since I thought I might be able to make something with a small footprint that I could share, which might be useful to others. The script grew and grew and became increasingly complex and I found I needed to add other scripts to the toolkit and to resort to Perl and various Perl modules to perform some actions.</p>
|
||||||
|
<p>Then in 2014 Ken changed the upload procedure to what it is now. This is a much better design and does away with the need to name files in odd ways and add metadata to them. However, this left my toolkit a bit high and dry, so I shelved the plans to release it.</p>
|
||||||
|
<p>Since then I have been enhancing the <code>hpr_talk</code> toolkit, adding features that I found useful and removing bugs, until the present time. Now it is probably far too complex and idiosyncratic to be of direct use to others, and is rather too personalised to my needs to be easily shared. Nevertheless, it is available on <a href="https://gitlab.com/davmo/hpr-talk" title="hpr_talk on GitLab">GitLab</a> and I am going to describe it here in case it (or the methods used) might be of interest to anyone.</p>
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
<p>As I have already said, the main script is called <code>hpr_talk</code>. It and its supporting scripts and files need to be unpacked into a directory where the shows are going to be stored. The principle is that each show and its related files are stored in their own sub-directory. The script derives the name of the directory from the title of the show it holds.</p>
|
||||||
|
<p>I keep my scripts and HPR episodes in the directory <code>~/HPR/Talks/</code>, but there's no problem with placing them anywhere so long as the directory is readable and writable by the account used to manage things, and everything is kept together.</p>
|
||||||
|
<p>The <code>hpr_talk</code> script takes one or two arguments. The first is an action word which specifies the function you are carrying out (such as <code>install</code> or <code>create</code>). The second argument varies with the function you are using and is optional. If present it can be the name of the directory holding your show or can be the title of a show.</p>
|
||||||
|
<p>If the argument is <code>-h</code> then the script displays a help message.</p>
|
||||||
|
<h2 id="my-work-flow">My Work-flow</h2>
|
||||||
|
<p>I have tried to give a fairly brief summary about how I use <code>hpr_talk</code> when creating and developing a episode for HPR. These are the main steps:</p>
|
||||||
|
<ol type="1">
|
||||||
|
<li><p>Create a new episode. I sometimes do this when I have an idea for a show, just as a placeholder. During creation the script collects all the metadata relating to the show by asking questions, setting defaults where I'm not yet sure of the answer.</p></li>
|
||||||
|
<li><p>Having created the show, I might mess around with the title, summary, tags and so forth before I'm happy with them. This can be done at any time in the life-cycle of the episode.</p></li>
|
||||||
|
<li><p>Mostly the shows I create consist of brief notes for the main page, and some longer notes available off that page (this one is an example). My notes are all prepared in Markdown, but I convert them to HTML before uploading. I might have example files, scripts and pictures in some cases. If I do then they and the long notes are packaged into a compressed TAR file for upload.</p></li>
|
||||||
|
<li><p>Since I'm generating HTML from Markdown I have added features to <code>hpr_talk</code> to aid me with this process. I create boilerplate Markdown files with the script, and I generate a Makefile which allows me to automate the build using GNU <code>make</code>. Since I use Vim to edit my notes I generate the configuration file necessary to run the <code>Session</code> plug-in. This way all I have to do is open the session in gVim and all the files are ready to edit.</p></li>
|
||||||
|
<li><p>If I am including multiple files in my show, such as pictures or scripts, I list them in a <em>manifest</em> file. The <code>hpr_talk</code> script allows me to create this file, and the Makefile uses it when generating the TAR file.</p></li>
|
||||||
|
<li><p>As I work on the notes I rebuild the HTML version and view it in a browser. The build process can be run out of Vim using the <code>make</code> command, or through the <code>hpr_talk</code> script on the command line. References to files and images are relative to the local environment at this stage, so the links work, but the command <code>make final</code> (or an appropriate script option) can be used to build everything to work on the HPR server.</p></li>
|
||||||
|
<li><p>The audio is generated independently, perhaps using Audacity or a portable recorder. I save the raw audio in the show directory and edit it and finally export it to the show directory. I use Audacity to save generic audio tags, but I have the means of generating specific tags automatically from the configuration file. This is not currently part of the system available on GitLab due to the number of dependencies.</p></li>
|
||||||
|
<li>As the components of a show accumulate the files are "<em>registered</em>" through a script function. The design is such that the primary elements are:
|
||||||
|
<ul>
|
||||||
|
<li>the main notes</li>
|
||||||
|
<li>the supplementary files bundled into a TAR file (as defined by the <em>manifest</em> file)</li>
|
||||||
|
<li>the audio</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><p>Once everything is ready to be uploaded I reserve a slot and then use <code>hpr_talk</code> to record the selection in the configuration file. This causes files to be renamed automatically to reflect the show they belong to. It also causes any HTML references to be changed in the notes when I perform the final build. I fill in the upload form manually with the details from the configuration, and the main notes. Then I use <code>hpr_talk</code> to perform the upload, which it does using curl to write to the FTP server.</p></li>
|
||||||
|
</ol>
|
||||||
|
<h2 id="features-of-the-hpr_talk-script">Features of the <code>hpr_talk</code> script</h2>
|
||||||
|
<p>The actions supported by the script are:</p>
|
||||||
|
<h3 id="install-the-script">Install the script</h3>
|
||||||
|
<p>This is something that is normally done only once. It sets up all of the configuration files needed by the script. It asks for the HPR host id and host name, so is really only appropriate if the user has already received these. This function also allows updating so is useful if something changes like the FTP password, for example.</p>
|
||||||
|
<h3 id="create-a-new-show">Create a new show</h3>
|
||||||
|
<p>As mentioned previously, the script will prompt for a number of parameters which it will store away in a show-specific configuration file in the show directory (which it will create). While the eventual show number (<em>slot</em>) is unknown the main files are called <code>hpr____.*</code> and then renamed when a slot has been reserved.</p>
|
||||||
|
<h3 id="change-the-configuration-of-a-show">Change the configuration of a show</h3>
|
||||||
|
<p>This function allows viewing and manipulation of the configuration of the show. For example, if you want to change the title, or update the summary, or when you have decided on the slot.</p>
|
||||||
|
<h3 id="advanced-configuration">Advanced configuration</h3>
|
||||||
|
<p>Here some of the more complex features can be set up:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Create the main show note template</li>
|
||||||
|
<li>Create the full (extended) show note template</li>
|
||||||
|
<li>Create and populate a manifest file (called <code>manifest</code>)</li>
|
||||||
|
<li>Create a Makefile to drive the building of the show files. Several options can be enabled here, such as whether to produce ePub notes</li>
|
||||||
|
<li>Create a Vim session. You need to be using Vim or gVim with the 'Session' plug-in to use this.</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="build-the-notes">Build the notes</h3>
|
||||||
|
<p>As already mentioned this function runs various types of <code>make</code> commands to perform the actions needed to build notes.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Build notes: build the main notes into HTML</li>
|
||||||
|
<li>Build all: build all of the options chosen for the show such as full notes, ePub notes, TAR file. Do this for local viewing</li>
|
||||||
|
<li>Build final: same as 'build all' but use URLs suitable for the HPR site</li>
|
||||||
|
<li>Touch all: refresh all files to force <code>make</code> to rebuild them</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="register-the-files-youre-going-to-upload">Register the files you're going to upload</h3>
|
||||||
|
<p>This lets you register the various files you have prepared for the show. It stores their details in the show configuration file.</p>
|
||||||
|
<h3 id="release-the-show">Release the show</h3>
|
||||||
|
<p>This is for uploading the show via FTP. You need to have registered everything in the configuration file first and should have built the final versions of everything.</p>
|
||||||
|
<h3 id="report-the-status-of-a-show-or-all-pending-shows">Report the status of a show or all pending shows</h3>
|
||||||
|
<p>There are two functions here. The first, <code>status</code>, prints a summary of the state of a show, mainly reporting the contents of the configuration file.</p>
|
||||||
|
<p>The <code>summary</code> function scans through all of the shows you have prepared but not uploaded and reports on their current state. This is useful if you are like me and have multiple ideas and partially completed shows stored in the main directory.</p>
|
||||||
|
<h2 id="example-use">Example use</h2>
|
||||||
|
<p>The following shows the output generated by <code>hpr_talk</code> when checking the status of this particular episode. The audio is done and registered, but a slot has not yet been chosen:</p>
|
||||||
|
<pre><code> cendjm@i7-desktop:~/HPR/Talks$ ./hpr_talk status How_I_prepare_HPR_shows
|
||||||
|
Status of an HPR talk
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
1: Hostid: 225
|
||||||
|
2: Hostname: Dave Morriss
|
||||||
|
3: Email: perloid@autistici.org
|
||||||
|
4: Project: How_I_prepare_HPR_shows
|
||||||
|
5: Title: How I prepare HPR shows
|
||||||
|
6: Slot: -
|
||||||
|
7: Sumadded: No
|
||||||
|
8: Inout: No
|
||||||
|
9: Series: -
|
||||||
|
10: Tags: Markdown,Pandoc,ePub,Bash,Perl,FTP
|
||||||
|
11: Explicit: Yes
|
||||||
|
12: Summary: I use my own tools for preparing my HPR shows. I talk about them in this episode
|
||||||
|
13: Notetype: HTML
|
||||||
|
14: Structure: Flat
|
||||||
|
15: Status: Editing
|
||||||
|
Dir: /home/cendjm/HPR/Talks/How_I_prepare_HPR_shows
|
||||||
|
Files: hpr____.flac
|
||||||
|
Files: hpr____.html
|
||||||
|
Files: hpr____.tbz
|
||||||
|
|
||||||
|
Size of HTML notes: 2429 characters</code></pre>
|
||||||
|
<h2 id="show-notes">Show Notes</h2>
|
||||||
|
<p>As already mentioned the notes are expected to be in Markdown format. The tool used to process the Markdown is <code>pandoc</code> and certain assumptions have been made about how this is run. For example, the main notes are built as an HTML fragment, while the extended notes are built <em>stand-alone</em>. Also the extended notes refer to the HPR site for their CSS to make them compatible with the HPR look and feel.</p>
|
||||||
|
<p>If the ePub option is chosen then certain assumptions are made about the layout of the end product. This part of the design is really in a state of flux at the moment and is very much attuned to my tastes.</p>
|
||||||
|
<h3 id="the-notes-are-really-a-template">The notes are really a template</h3>
|
||||||
|
<p>The main and supplementary notes files are both passed through a pre-processor before being read by <code>pandoc</code>. This is a Perl script which interprets expressions in the <em><a href="http://template-toolkit.org/about.html" title="Template Toolkit">Template Toolkit</a></em> syntax. The pre-processor is given arguments consisting of the names of files such as the extended notes and the contents of the <code>manifest</code> file. This simplifies the process of linking to supplementary files and images and allows the generated URLs to change depending on whether the HTML is for local viewing or is the final version for upload.</p>
|
||||||
|
<p>For example, my main notes file often ends with text such as the following:</p>
|
||||||
|
<pre><code>I have written out a moderately long set of notes about this subject and these
|
||||||
|
are available here [[% args.0 %]]([% args.0 %]).</code></pre>
|
||||||
|
<p>Here <code>[% args.0 %]</code> is an expression which substitutes the first argument to the pre-processor into the text. These expressions are enclosed in a Markdown link in this example.</p>
|
||||||
|
<p>I also recently prepared a show with multiple images and inserted them thus:</p>
|
||||||
|
<pre><code>[%- DEFAULT i = 0 -%]
|
||||||
|
\
|
||||||
|
*Slice the carrots diagonally*
|
||||||
|
|
||||||
|
\
|
||||||
|
*The slices should be moderately thick, about 5mm*</code></pre>
|
||||||
|
<p>This generates the argument references programmatically, which I found was easier to manage when there were 30+ images.</p>
|
||||||
|
<h2 id="conclusion">Conclusion</h2>
|
||||||
|
<p>The toolkit described here does a large amount of what I want when preparing HPR shows, and saves me from my failing memory. The present design still shows the signs of its origin in the days before the current submission mechanism, and this will be corrected in time.</p>
|
||||||
|
<p>I'd like to automate the completion of the submission form in a later version, though whether Ken will appreciate me attaching scripts to it I doubt.</p>
|
||||||
|
<p>You are welcome to try the scripts out if you want; that's why it's on GitLab. There is not much documentation at the moment, but I am adding to it gradually. Please contact me if you have problems or suggestions on how to improve the project.</p>
|
||||||
|
<h2 id="links">Links</h2>
|
||||||
|
<ul>
|
||||||
|
<li>My <em>hpr_talk</em> toolkit on GitLab: <a href="https://gitlab.com/davmo/hpr-talk" class="uri">https://gitlab.com/davmo/hpr-talk</a></li>
|
||||||
|
<li>The <em>Template Toolkit</em> for Perl and Python: <a href="http://template-toolkit.org/about.html" class="uri">http://template-toolkit.org/about.html</a></li>
|
||||||
|
</ul>
|
||||||
|
<!--
|
||||||
|
vim: syntax=markdown:ts=8:sw=4:ai:et:tw=78:fo=tcqn:fdm=marker
|
||||||
|
-->
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
eps/hpr1941/hpr1941_J_Herbin_1.png
Executable file
|
After Width: | Height: | Size: 341 KiB |
BIN
eps/hpr1941/hpr1941_J_Herbin_2.png
Executable file
|
After Width: | Height: | Size: 353 KiB |
BIN
eps/hpr1941/hpr1941_J_Herbin_3.png
Executable file
|
After Width: | Height: | Size: 354 KiB |
BIN
eps/hpr1941/hpr1941_J_Herbin_writing_s.png
Executable file
|
After Width: | Height: | Size: 222 KiB |
BIN
eps/hpr1941/hpr1941_Noodlers_Konrad_1.png
Executable file
|
After Width: | Height: | Size: 326 KiB |
BIN
eps/hpr1941/hpr1941_Noodlers_Konrad_2.png
Executable file
|
After Width: | Height: | Size: 332 KiB |
BIN
eps/hpr1941/hpr1941_Noodlers_Konrad_3.png
Executable file
|
After Width: | Height: | Size: 346 KiB |
BIN
eps/hpr1941/hpr1941_Noodlers_Konrad_writing_s.png
Executable file
|
After Width: | Height: | Size: 254 KiB |
BIN
eps/hpr1941/hpr1941_Pelikan_M215_1.png
Executable file
|
After Width: | Height: | Size: 318 KiB |
BIN
eps/hpr1941/hpr1941_Pelikan_M215_2.png
Executable file
|
After Width: | Height: | Size: 338 KiB |
BIN
eps/hpr1941/hpr1941_Pelikan_M215_3.png
Executable file
|
After Width: | Height: | Size: 334 KiB |
BIN
eps/hpr1941/hpr1941_Pelikan_M215_writing_s.png
Executable file
|
After Width: | Height: | Size: 271 KiB |
BIN
eps/hpr1941/hpr1941_Pen_Case_1.png
Executable file
|
After Width: | Height: | Size: 416 KiB |
BIN
eps/hpr1941/hpr1941_Pen_Case_2.png
Executable file
|
After Width: | Height: | Size: 427 KiB |
BIN
eps/hpr1941/hpr1941_Pilot_MR_1.png
Executable file
|
After Width: | Height: | Size: 290 KiB |