{"id":474,"date":"2008-10-30T17:20:49","date_gmt":"2008-10-31T00:20:49","guid":{"rendered":"http:\/\/www.stevenbrown.ca\/blog\/?p=474"},"modified":"2008-11-18T12:23:31","modified_gmt":"2008-11-18T20:23:31","slug":"open-with-for-the-command-line","status":"publish","type":"post","link":"https:\/\/www.stevenbrown.ca\/blog\/archives\/474","title":{"rendered":"open-with for the command-line"},"content":{"rendered":"<p><strong>Update 2008\/11\/18<\/strong>: Use <code>xargs<\/code> \ud83d\ude1b<\/p>\n<p>Here&#8217;s a bash script that you can pipe output into and tell it to run a specific program with the output as arguments.  I&#8217;ve named it <code>open-with<\/code> and placed it in my personal script directory: <code>\/home\/steve\/bin\/<\/code>.  Look within the script at a couple of the examples for how to use it.<\/p>\n<pre lang=\"bash\">\r\n#!\/bin\/bash\r\n\r\nPROG=`basename $0`\r\n\r\nDESC=\"Read arguments from standard input and run a specified program\r\nwith them.  Meant to be used as output for a pipe.\"\r\n\r\n\r\nUSAGE=\"OUTPUT | $PROG \\\"PROGRAM-NAME [OPTIONS AND ARGUMENTS]\\\"\"\r\n\r\nEXAMPLES=\"\r\n# List (with details), all the files that include \\\"downloads\\\" in their name:\r\n  find \/home\/ -iname \\\"*downloads*\\\" | $PROG \\\"ls -l\\\"\r\n\r\n# Queue all AVI files in current directory in vlc:\r\n  ls *.avi | $PROG vlc\r\n\r\n# View all time-related icons with eye-of-gnome:\r\n  find \/usr\/share\/icons\/gnome\/ -iregex \\\".*[^mul]time.*\\\" | open-with eog\r\n\"\r\n\r\nfunction Usage() {\r\n  echo \"DESCRIPTION: $DESC\" ; echo\r\n  echo \"USAGE: $USAGE\" ; echo\r\n  echo -n \"EXAMPLES: $EXAMPLES\"\r\n}\r\n\r\n# Quick check to see it's being called correctly, if not, print Usage and exit\r\nif [ ${#@} -ne 1 ]\r\nthen\r\n\tUsage\r\n\texit\r\nfi\r\n\r\nfiles=()\t\t\t\t\t\t# empty array\r\nwhile read -r\t\t\t\t\t# read from stdin\r\ndo\r\n\tfiles+=( \"$REPLY\" )\t\t\t# add result of read to array\r\ndone\r\n\r\n# assume $1 is a valid program\r\n$1 \"${files[@]}\"\t\t\t\t# pass arguments to specified program\r\n<\/pre>\n<h3>Motivation<\/h3>\n<p>You&#8217;re sitting at the command line and have a list of images (in different locations) that you would like to browse.  Most image viewers let you iterate over a set of images, but only within the same directory.  What would be great is if they accepted input from stdin through a pipe!<\/p>\n<pre lang=\"bash\">\r\n$ cat my_list_of_images | my_image_viewer\r\n<\/pre>\n<p>I didn&#8217;t find anything that did that, but using the <code>open-with<\/code> script, you can do something similar:  <\/p>\n<pre lang=\"bash\">\r\n$ cat my_list_of_images | open-with my_image_viewer\r\n<\/pre>\n<p><a href=\"https:\/\/i0.wp.com\/www.stevenbrown.ca\/blog\/wordpress\/..\/files\/2008\/10\/open-with-eog.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" data-attachment-id=\"482\" data-permalink=\"https:\/\/www.stevenbrown.ca\/blog\/archives\/474\/open-with-eog\" data-orig-file=\"https:\/\/i0.wp.com\/www.stevenbrown.ca\/blog\/wordpress\/..\/files\/\/home\/.kelb\/stiibu\/stevenbrown.ca\/blog\/wordpress\/..\/files\/2008\/10\/open-with-eog.png?fit=450%2C379&amp;ssl=1\" data-orig-size=\"450,379\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;}\" data-image-title=\"open-with-eog\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/www.stevenbrown.ca\/blog\/wordpress\/..\/files\/\/home\/.kelb\/stiibu\/stevenbrown.ca\/blog\/wordpress\/..\/files\/2008\/10\/open-with-eog.png?fit=300%2C252&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/www.stevenbrown.ca\/blog\/wordpress\/..\/files\/\/home\/.kelb\/stiibu\/stevenbrown.ca\/blog\/wordpress\/..\/files\/2008\/10\/open-with-eog.png?fit=450%2C379&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/www.stevenbrown.ca\/blog\/wordpress\/..\/files\/2008\/10\/open-with-eog-300x252.png?resize=300%2C252&#038;ssl=1\" alt=\"\" title=\"open-with-eog\" width=\"300\" height=\"252\" class=\"alignnone size-medium wp-image-482\" \/><\/a><br \/>\nBrowsing a select list of images is actually kind of nice.  It&#8217;s like a playlist for your image viewer &#8211; a viewlist.  \ud83d\ude42  Anyway, I&#8217;m sure there&#8217;s some problems with this script.  Feel free to provide suggestions in the comments.  But I certainly don&#8217;t want to look at it for a while&#8230;.<\/p>\n<h3>I hate shell scripting<\/h3>\n<p>With a passion.  It&#8217;s not a great surprise&#8230; many programmers do.  I&#8217;m willing to go on record and state that I hate it even more than PERL programming.  I rarely do it, and when I decide to try something that seems like it would be simple, it turns out taking forever due mostly to quirks.  The rest of this post is a bit about how I went about writing this script, which ended up being mostly given to me by some <code>#bash<\/code> gurus on IRC.  And it&#8217;s a bit of a rant.<\/p>\n<p>Although not many GUI programs seem to accept stdin as input, most accept filenames as arguments:<\/p>\n<pre lang=\"bash\">\r\n$ eog image1.jpg image2.jpg image3.jpg \"\/home\/steve\/image seven.jpg\"\r\n<\/pre>\n<p>So I figured I would just have to convert the list of images into an acceptable format: quoted, absolute filenames, separated by space.  Doing this depends entirely on the format the list is currently in, but it&#8217;s likely a list of unquoted filenames separated by newlines:<\/p>\n<pre>\r\n\/home\/steve\/image1.jpg\r\n\/home\/steve\/my images\/wow.jpg\r\n\/tmp\/anotherimage.png\r\n<\/pre>\n<p>For me, my test list looked like this:<\/p>\n<pre>\r\n\/usr\/share\/icons\/gnome\/48x48\/stock\/generic\/stock_timezone.png\r\n\/usr\/share\/icons\/gnome\/16x16\/stock\/generic\/stock_timezone.png\r\n\/usr\/share\/icons\/gnome\/16x16\/stock\/generic\/stock_timer.png\r\n\/usr\/share\/icons\/gnome\/16x16\/stock\/generic\/stock_timer_stopped.png\r\n\/usr\/share\/icons\/gnome\/16x16\/stock\/form\/stock_form-time-field.png\r\n\/usr\/share\/icons\/gnome\/24x24\/stock\/generic\/stock_timezone.png\r\n\/usr\/share\/icons\/gnome\/24x24\/stock\/generic\/stock_timer.png\r\n\/usr\/share\/icons\/gnome\/24x24\/stock\/generic\/stock_timer_stopped.png\r\n\/usr\/share\/icons\/gnome\/24x24\/stock\/form\/stock_form-time-field.png\r\n<\/pre>\n<p>I was looking for icons of <em>clocks<\/em> or representations of <em>time<\/em>, and I obtained that list using <code>find<\/code>:<\/p>\n<pre lang=\"bash\">\r\n$ find \/usr\/share\/icons\/gnome\/ -iregex \".*[^mul]time.*\"\r\n<\/pre>\n<p>So I can&#8217;t pipe it into my image viewer, but I can use the output as the command-line arguments if I change the newlines to spaces.  <code>find<\/code> has an option for formatting the output which is perfect:<\/p>\n<pre lang=\"bash\">\r\n$ find \/usr\/share\/icons\/gnome\/ -iregex \".*[^mul]time.*\" -printf \"'%p' \"\r\n'\/usr\/share\/icons\/gnome\/48x48\/stock\/generic\/stock_timezone.png' '\/usr\/share\/icons\/gnome\/16x16\/stock\/generic\/stock_timezone.png' '\/usr\/share\/icons\/gnome\/16x16\/stock\/generic\/stock_timer.png' '\/usr\/share\/icons\/gnome\/16x16\/stock\/generic\/stock_timer_stopped.png' '\/usr\/share\/icons\/gnome\/16x16\/stock\/form\/stock_form-time-field.png' '\/usr\/share\/icons\/gnome\/24x24\/stock\/generic\/stock_timezone.png' '\/usr\/share\/icons\/gnome\/24x24\/stock\/generic\/stock_timer.png' '\/usr\/share\/icons\/gnome\/24x24\/stock\/generic\/stock_timer_stopped.png' '\/usr\/share\/icons\/gnome\/24x24\/stock\/form\/stock_form-time-field.png' \r\n<\/pre>\n<p>That&#8217;s great for me, because I&#8217;m using <em><code>find<\/code><\/em>.  But not very useful if I&#8217;m not, so I wanted something more generic.  Of course, there are many ways to do this, and again, I&#8217;m by no means a command-line guru.  But here&#8217;s how I started:<\/p>\n<pre lang=\"bash\">\r\n$ find \/usr\/share\/icons\/gnome\/ -iregex \".*[^mul]time.*\" | sed -e 's\/^\/\"\/' | sed -e :a -e '$!N;s\/\\n\/\" \/; ta' | tr '\\n' '\"'\r\n<\/pre>\n<p>This wrapped the lines in double-quotes and join them together with a space in between.  The <code>find<\/code> command is the same as before, minus the formatting because that&#8217;s what I was trying to find an alternative to.  The output of <code>find<\/code> is piped into <code>sed<\/code>, which adds a <code>\"<\/code> at the beginning of each line.  This output is then sent to another <code>sed<\/code> which replaces the newline character at the end of each line with a closing double-quote and a space, joining all lines into a single line.  Finally, that output is piped into <code>tr<\/code> which replaces the one remaining newline with a final double-quote.<\/p>\n<p>If the files don&#8217;t include spaces or other troublesome characters (mine didn&#8217;t), then you could get away with simply changing the newlines into spaces.  But again, I wanted something generic.<\/p>\n<pre lang=\"bash\">\r\n$ find \/usr\/share\/icons\/gnome\/ -iregex \".*[^mul]time.*\" | tr '\\n' ' '\r\n<\/pre>\n<p>Anyway, now that we have something that creates the desired input, we just have to wrap it in back-ticks and put the whole mess as the argument to the image viewer!  In my case, I&#8217;m using eye-of-gnome (or eog).<\/p>\n<pre lang=\"bash\">\r\n$ eog `find \/usr\/share\/icons\/gnome\/ -iregex \".*[^mul]time.*\" | sed -e 's\/^\/\"\/' | sed -e :a -e '$!N;s\/\\n\/\" \/; ta' | tr '\\n' '\"'`\r\n<\/pre>\n<p>Wait a second.  That doesn&#8217;t actually work.  Why not?  Running the backtick contents by itself seemed to produce the correct output.  Copying this output verbatim as arguments to <code>eog<\/code> worked as expected.  The problem was that when the quoted arguments were manually entered on the command-line, bash (silently) escapes the filenames, removing the quotes, adding backslashes before space,s etc.  But when the pipeline is wrapped in backticks, the result is not escaped, and <code>eog<\/code> complains about not being able to find files that begin with quotes.  Fine.  I was not about to write a bash &#8220;escape&#8221; script &#8211; something like that should already exist, right?  And it should be built in to bash!  Perhaps it&#8217;s even called &#8220;<code>escape<\/code>&#8220;.  Well, if there exists such a built-in, I couldn&#8217;t find it.  But I had to be going about this the wrong way.  Heading over to IRC, it was kind of difficult to explain the problem, but <code>ferret<\/code> eventually gave me the meat of the script above:<\/p>\n<pre lang=\"bash\">\r\nfiles=(); while read -r; do files+=( \"$REPLY\" ); done < <(find \/usr\/share\/icons\/gnome\/ -iregex \".*[^mul]time.*\"); eog \"${files[@]}\"\r\n<\/pre>\n<p>It worked!  I thanked him and began studying it.  There were still a couple things I didn't understand:<\/p>\n<ul>\n<li>Why two angle brackets with a space between them? I understand the first one is probably redirecting input, but I don't understand that one next to the opening parenthesis.  <br \/><strong>Answer:<\/strong> The <code>&lt;(command)<\/code> actually puts <code>command<\/code>'s output in a temporary file and produces that filename.  So <code>&lt;(command)<\/code> becomes <code>tempfile<\/code>.  (Thanks, <code>kojiro<\/code>.)<\/li>\n<li>If I open some video or audio files in <code>totem<\/code> using <code>open-with<\/code>, there's an odd delay.  Using <code>vlc<\/code> doesn't produce a delay, however.<\/li>\n<\/ul>\n<p>And to think this was originally going to be a micro-blog post in Twitter and identi.ca.  Wow.  It's safe to say I still hate shell scripting.  :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Update 2008\/11\/18: Use xargs \ud83d\ude1b Here&#8217;s a bash script that you can pipe output into and tell it to run a specific program with the output as arguments. I&#8217;ve named it open-with and placed it in my personal script directory: \/home\/steve\/bin\/. Look within the script at a couple of the examples for how to use&hellip; <a class=\"more-link\" href=\"https:\/\/www.stevenbrown.ca\/blog\/archives\/474\">Continue reading <span class=\"screen-reader-text\">open-with for the command-line<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[3,11],"tags":[197,48,196,42,4,92,29,176],"class_list":["post-474","post","type-post","status-publish","format-standard","hentry","category-geek","category-projects","tag-bash","tag-code","tag-eog","tag-how-to","tag-linux","tag-rants","tag-screenshots","tag-scripts","entry"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4jEMb-7E","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/posts\/474","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/comments?post=474"}],"version-history":[{"count":12,"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/posts\/474\/revisions"}],"predecessor-version":[{"id":496,"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/posts\/474\/revisions\/496"}],"wp:attachment":[{"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/media?parent=474"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/categories?post=474"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.stevenbrown.ca\/blog\/wp-json\/wp\/v2\/tags?post=474"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}