PageRenderTime 67ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/index.html

https://github.com/jneen/jayferd.github.com
HTML | 367 lines | 256 code | 106 blank | 5 comment | 0 complexity | 5f9eca5cca81c774422b2c2a4dc955b7 MD5 | raw file
  1. <!DOCTYPE html>
  2. <!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
  3. <!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
  4. <!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!--><html class="no-js" lang="en"><!--<![endif]-->
  5. <head>
  6. <meta charset="utf-8">
  7. <title>Things I Remember to Write Down: Bash adventures: read a single character, even if it's a newline</title>
  8. <meta name="author" content="Jay Adkisson">
  9. <!-- http://t.co/dKP3o1e -->
  10. <meta name="HandheldFriendly" content="True">
  11. <meta name="MobileOptimized" content="320">
  12. <meta name="viewport" content="width=device-width, initial-scale=1">
  13. <link rel="canonical" href="http://write.jayferd.us/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/"/>
  14. <link href="/favicon.png" rel="shortcut icon" />
  15. <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
  16. <script src="/javascripts/modernizr-2.0.js"></script>
  17. <script src="http://s3.amazonaws.com/ender-js/jeesh.min.js"></script>
  18. <script src="/javascripts/octopress.js" type="text/javascript"></script>
  19. <link href="/atom.xml" rel="alternate" title="Things I Remember to Write Down" type="application/atom+xml"/>
  20. <script type="text/javascript">
  21. (function() {
  22. var script = document.createElement('script'); script.type = 'text/javascript'; script.async = true;
  23. script.src = 'https://apis.google.com/js/plusone.js';
  24. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(script, s);
  25. })();
  26. </script>
  27. <script type="text/javascript">
  28. (function(){
  29. var twitterWidgets = document.createElement('script');
  30. twitterWidgets.type = 'text/javascript';
  31. twitterWidgets.async = true;
  32. twitterWidgets.src = 'http://platform.twitter.com/widgets.js';
  33. document.getElementsByTagName('head')[0].appendChild(twitterWidgets);
  34. })();
  35. </script>
  36. <!--Fonts from Google's Web font directory at http://google.com/webfonts -->
  37. <link href='http://fonts.googleapis.com/css?family=PT+Serif:regular,italic,bold,bolditalic' rel='stylesheet' type='text/css'>
  38. <link href='http://fonts.googleapis.com/css?family=PT+Sans:regular,italic,bold,bolditalic' rel='stylesheet' type='text/css'>
  39. </head>
  40. <body >
  41. <header><hgroup>
  42. <h1><a href="/">Things I Remember to Write Down</a></h1>
  43. </hgroup>
  44. </header>
  45. <nav role=navigation><ul role=subscription data-subscription="rss">
  46. <li><a href="/atom.xml" rel="subscribe-rss" title="subscribe via RSS">RSS</a></li>
  47. </ul>
  48. <form action="http://google.com/search" method="get">
  49. <fieldset role="site-search">
  50. <input type="hidden" name="q" value="site:write.jayferd.us" />
  51. <input class="search" type="text" name="q" results="0" placeholder="Search"/>
  52. </fieldset>
  53. </form>
  54. <ul role=main-navigation>
  55. <li><a href="/">Blog</a></li>
  56. <li><a href="/blog/archives">Archives</a></li>
  57. </ul>
  58. </nav>
  59. <div id="main">
  60. <div id="content">
  61. <div>
  62. <article class="hentry">
  63. <header>
  64. <h1 class="entry-title">Bash Adventures: Read a Single Character, Even if It&#8217;s a Newline</h1>
  65. <p class="meta">
  66. <time datetime="2011-01-12 14:16:00 -0800" pubdate updated >Jan 12<span>th</span>, 2011</time>
  67. </p>
  68. </header>
  69. <div class="entry-content"><h3>tl;dr</h3>
  70. <div class="highlight"><pre><code class="bash">getc<span class="o">()</span> <span class="o">{</span>
  71. <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -r -n1 -d <span class="s1">&#39;&#39;</span> <span class="s2">&quot;$@&quot;</span>
  72. <span class="o">}</span>
  73. getc ch
  74. <span class="nb">echo</span> <span class="s2">&quot;$ch&quot;</span> <span class="c"># don&#39;t forget to quote it!</span>
  75. </code></pre>
  76. </div>
  77. <h3>the explanation</h3>
  78. <!-- more -->
  79. <p>So it turns out that it&#8217;s pretty difficult to read exactly one character from a stream.
  80. From the manual for <code>read</code> (usually in <code>man builtins</code>):</p>
  81. <pre><code>-n nchars
  82. read returns after reading nchars characters rather than
  83. waiting for a complete line of input, but honor a delim
  84. iter if fewer than nchars characters are read before the
  85. delimiter.
  86. </code></pre>
  87. <p>The naive solution, then would be:</p>
  88. <div class="highlight"><pre><code class="bash">getc<span class="o">()</span> <span class="o">{</span>
  89. <span class="nb">read</span> -n1 <span class="s2">&quot;$@&quot;</span>
  90. <span class="o">}</span>
  91. <span class="k">while </span>getc ch; <span class="k">do</span>
  92. <span class="k"> </span><span class="nb">echo</span> <span class="s2">&quot;[$ch]&quot;</span>;
  93. <span class="k">done</span> <span class="s">&lt;&lt;EOF</span>
  94. <span class="s">one</span>
  95. <span class="s">two</span>
  96. <span class="s">three</span>
  97. <span class="s">four</span>
  98. <span class="s">EOF</span>
  99. </code></pre>
  100. </div>
  101. <p>The output from this is:</p>
  102. <pre><code>[o]
  103. [n]
  104. [e]
  105. []
  106. [t]
  107. [w]
  108. [o]
  109. []
  110. [t]
  111. [h]
  112. [r]
  113. [e]
  114. [e]
  115. []
  116. [f]
  117. [o]
  118. [u]
  119. [r]
  120. []
  121. </code></pre>
  122. <p>Wait, what happened to the newlines &#8211; why are they returning empty characters? Well, it turns out there are two things going on here. Again from the manual:</p>
  123. <pre><code>The characters in the value of the IFS variable are used to split the line into words.
  124. </code></pre>
  125. <p>&#8230;and the newline is in the IFS. So let&#8217;s zero out <code>IFS</code>:</p>
  126. <div class="highlight"><pre><code class="bash">getc<span class="o">()</span> <span class="o">{</span>
  127. <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -n1 <span class="s2">&quot;$@&quot;</span>
  128. <span class="o">}</span>
  129. </code></pre>
  130. </div>
  131. <p>unfortunately, this outputs the same thing, but for a different reason.
  132. Remember: <code>read -n nchars</code> will still &quot;honor a delimiter if fewer than <code>nchars</code> characters are read before the delimiter&quot;.
  133. And the default delimiter (specified with <code>-d delim</code>) is the newline.
  134. So the next solution is to use an empty delimiter.</p>
  135. <p>Now our <code>getc</code> looks like:</p>
  136. <div class="highlight"><pre><code class="bash">getc<span class="o">()</span> <span class="o">{</span>
  137. <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -n1 -d <span class="s1">&#39;&#39;</span> <span class="s2">&quot;$@&quot;</span>
  138. <span class="o">}</span>
  139. </code></pre>
  140. </div>
  141. <p>(NOTE: the space between the <code>-d</code> and the <code>''</code> is not optional. This confused me the first time I was solving the problem, and I had suggested using the EOF character. Not only that, I suggested obtaining an EOF with <code>&quot;$(echo -e '\004')</code>. The empty delimiter is a better way to solve this particular problem, and <code>$'\004'</code> is a better way to get an EOF character.)</p>
  142. <p>Now we get a very different output:</p>
  143. <pre><code>[o]
  144. [n]
  145. [e]
  146. [
  147. ]
  148. [t]
  149. [w]
  150. [o]
  151. [
  152. ]
  153. [t]
  154. [h]
  155. [r]
  156. [e]
  157. [e]
  158. [
  159. ]
  160. [f]
  161. [o]
  162. [u]
  163. [r]
  164. [
  165. ]
  166. </code></pre>
  167. <p>Our newlines are back! Yay!</p>
  168. <p>There is, however, one more wrinkle, as helpfully pointed out by an anonymous commenter. <code>read</code> also likes to let you use backslashes to escape things. For example, with the current implementation of <code>getc</code>:</p>
  169. <div class="highlight"><pre><code class="bash"><span class="k">while </span>getc ch; <span class="k">do</span>
  170. <span class="k"> </span><span class="nb">echo</span> <span class="s2">&quot;[$ch]&quot;</span>
  171. <span class="k">done</span> <span class="s">&lt;&lt;EOF</span>
  172. <span class="s">one\</span>
  173. <span class="s">two</span>
  174. <span class="s">EOF</span>
  175. </code></pre>
  176. </div>
  177. <p>Yields the following output:</p>
  178. <pre><code>[o]
  179. [n]
  180. [e]
  181. [t]
  182. [w]
  183. [o]
  184. [
  185. ]
  186. </code></pre>
  187. <p>Note how the backslash after <code>one</code> was interpreted as an escape for the newline that follows. Luckily, <code>read</code> comes with an <code>-r</code> option:</p>
  188. <pre><code>-r Backslash does not act as an escape character. The back
  189. slash is considered to be part of the line. In particu
  190. lar, a backslash-newline pair may not be used as a line
  191. continuation.
  192. </code></pre>
  193. <p>So the final solution is:</p>
  194. <div class="highlight"><pre><code class="bash">getc<span class="o">()</span> <span class="o">{</span>
  195. <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -r -n1 -d <span class="s1">&#39;&#39;</span> <span class="s2">&quot;$@&quot;</span>
  196. <span class="o">}</span>
  197. </code></pre>
  198. </div>
  199. <p>EDIT 7/19/2011: Fixes and nuances to the original solution, as provided by <code>:)</code>.</p>
  200. </div>
  201. <footer>
  202. <p class="meta">
  203. <span class="byline author vcard">Posted by <span class="fn">Jay Adkisson</span></span>
  204. <time datetime="2011-01-12 14:16:00 -0800" pubdate updated >Jan 12<span>th</span>, 2011</time>
  205. </p>
  206. <div class="sharing">
  207. <a href="http://twitter.com/share" class="twitter-share-button" data-url="http://write.jayferd.us/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/" data-via="jayferd" data-counturl="http://write.jayferd.us/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/" >Tweet</a>
  208. <g:plusone size="medium"></g:plusone>
  209. </div>
  210. </footer>
  211. </article>
  212. <section>
  213. <h1>Comments</h1>
  214. <div id="disqus_thread"><div id="disqus_thread"></div>
  215. <script type="text/javascript">
  216. var disqus_shortname = 'writedown';
  217. var disqus_identifier = 'http://write.jayferd.us/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/';
  218. var disqus_url = 'http://write.jayferd.us/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/';
  219. //var disqus_developer = 1;
  220. (function() {
  221. var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
  222. dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
  223. (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  224. })();
  225. </script>
  226. <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
  227. </div>
  228. </section>
  229. </div>
  230. <aside role=sidebar>
  231. <section>
  232. <h1>Recent Posts</h1>
  233. <ul id="recent_posts">
  234. <li class="post">
  235. <a href="/blog/2011/01/12/bash-adventures-read-a-single-character-even-if-its-a-newline/">Bash adventures: read a single character, even if it&#8217;s a newline</a>
  236. </li>
  237. </ul>
  238. </section>
  239. <section>
  240. <h1>Latest Tweets</h1>
  241. <ul id="tweets">
  242. <li class="loading">Status updating&#8230;</li>
  243. </ul>
  244. <script type="text/javascript">
  245. $.domReady(function(){
  246. getTwitterFeed("jayferd", 4, false);
  247. });
  248. </script>
  249. <script src="/javascripts/twitter.js" type="text/javascript"> </script>
  250. <a href="http://twitter.com/jayferd" class="twitter-follow-button" data-width="208px" data-show-count="false">Follow @jayferd</a>
  251. </section>
  252. </aside>
  253. </div>
  254. </div>
  255. <footer><p>
  256. Copyright &copy; 2011 - Jay Adkisson -
  257. <span class="credit">Powered by <a href="http://octopress.org">Octopress</a></span>
  258. </p>
  259. </footer>
  260. </body>
  261. </html>