{"id":1803,"date":"2018-08-31T09:47:10","date_gmt":"2018-08-31T07:47:10","guid":{"rendered":"http:\/\/www.ludovicocaldara.net\/dba\/?p=1803"},"modified":"2020-08-18T16:07:16","modified_gmt":"2020-08-18T14:07:16","slug":"bash-sqlplus-to-ords","status":"publish","type":"post","link":"https:\/\/www.ludovicocaldara.net\/dba\/bash-sqlplus-to-ords\/","title":{"rendered":"Converting SQL*Plus calls in shell scripts to ORDS calls"},"content":{"rendered":"<p>I develop a lot of shell scripts. I would not define myself an old dinosaur that keeps avoiding python or other modern languages. It is just that most of my scripts automate OS commands that I would normally run interactively in an interactive shell&#8230; tar, cp, expdp, rman, dgmgrl, etc&#8230; and of course,\u00a0some SQL*Plus executions.<\/p>\n<p>For database calls, the shell is\u00a0not\u00a0appropriate: no drivers, no connection, no statement, no resultset&#8230; that&#8217;s why I need to make SQL*Plus executions (with some hacks to make them work correctly), and that&#8217;s also why I normally\u00a0use python or perl for data-related tasks.<\/p>\n<p><strong>Using SQL*Plus in shell scripts<\/strong><\/p>\n<p>For SQL*Plus executions within a shell scripts there are some hacks, as I have said, that allow to get the data correctly.<\/p>\n<p>As example, let&#8217;s use this table (that you might have found in my recent posts):<\/p>\n<pre class=\"lang:plsql decode:true\">SQL&gt; desc OH_GOLDEN_IMAGES\r\n Name                                      Null?    Type\r\n ----------------------------------------- -------- ----------------------------\r\n NAME                                      NOT NULL VARCHAR2(50)\r\n OH_TYPE                                            VARCHAR2(10)\r\n VERSION                                            VARCHAR2(10)\r\n FULLPATH                                           VARCHAR2(200)\r\n CREATED                                            TIMESTAMP(6)\r\n DESCRIPTION                                        VARCHAR2(2000)\r\n\r\nSQL&gt; insert into OH_GOLDEN_IMAGES values ('18_3_0_cerndb1', 'RDBMS', '18.3.0', '\/test\/path\/18_3_0_cerndb1.zip', sysdate-10, 'First version 18.3.0');\r\n\r\n1 row created.\r\n\r\nSQL&gt; insert into OH_GOLDEN_IMAGES values ('18_3_0_cerndb2', 'RDBMS', '18.3.0', '\/test\/path\/18_3_0_cerndb2.zip', sysdate-1, '18_3_0_cerndb1 + Patch XXX');\r\n\r\n1 row created.\r\n\r\nSQL&gt; commit;\r\n\r\nCommit complete.<\/pre>\n<p>In order to get, as example, the result of this query:<\/p>\n<pre class=\"lang:plsql decode:true\">SELECT name, version, fullpath, TO_CHAR(created,'YYYY-MM-DD') as created\r\nFROM oh_golden_images WHERE oh_type='RDBMS' order by created<\/pre>\n<p>and assign the values to some variables (in a shell loop), it is common to do something like this:<\/p>\n<pre class=\"lang:sh decode:true\">REPO_CREDENTIALS='scott\/tiger@orcl'\r\nRESULT=`$ORACLE_HOME\/bin\/sqlplus -s $REPO_CREDENTIALS 2&gt;&amp;1 &lt;&lt;EOF | grep \";\"\r\n        set line 200 pages 1000\r\n        set echo off feedback off heading off\r\n        alter session set nls_timestamp_format='YYYY-MM-DD';\r\n        SELECT name || ';' ||version || ';' || fullpath || ';' || created\r\n          FROM oh_golden_images\r\n        WHERE oh_type='RDBMS'\r\n           order by created;\r\n        exit;\r\nEOF\r\n`\r\n\r\nfor line in $RESULT ; do\r\n        L_GI_Name=`echo $line | awk -F\\; '{print $1}'`\r\n        L_GI_Version=`echo $line | awk -F\\; '{print $2}'`\r\n        L_GI_Path=`echo $line | awk -F\\; '{print $3}'`\r\n        L_GI_Date=`echo $line | awk -F\\; '{print $4}'`\r\n        echo \"doing something with variables $L_GI_Name $L_GI_Date $L_GI_Path $L_GI_Version\"\r\ndone\r\n<\/pre>\n<p>As you can see, there are several hacks:<\/p>\n<ul>\n<li>The credentials must be defined somewhere (I recommend putting them <a href=\"https:\/\/www.ludovicocaldara.net\/dba\/wallets-and-proxy-users\/\">in a wallet<\/a>)<\/li>\n<li>All the output goes in a variable (or looping directly)<\/li>\n<li>SQL*Plus formatting can be a problem (both sqlplus settings and concatenating fields)<\/li>\n<li>Loop and get, for each line, the variables (using awk in my case)<\/li>\n<\/ul>\n<p>It is not rock solid (unexpected data might compromise the results) and there are dependencies (sqlplus binary, credentials, etc.). But for many simple tasks, that&#8217;s more than enough.<\/p>\n<p>Here&#8217;s the output:<\/p>\n<pre class=\"lang:plsql highlight:0 decode:true \">$ sh sqlplus_test.sh\r\ndoing something with values 18_3_0_cerndb1 2018-08-19 \/test\/path\/18_3_0_cerndb1.zip 18.3.0\r\ndoing something with values 18_3_0_cerndb2 2018-08-28 \/test\/path\/18_3_0_cerndb2.zip 18.3.0\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Using ORDS instead<\/strong><\/p>\n<p>Recently I have come across a situation where I had no Oracle binaries but needed to get some data from a table. That is often a situation where I use python or perl, but even in these cases, I\u00a0need compatible software and drivers!<\/p>\n<p>So I used ORDS instead (that by chance, was already configured for the databases I wanted to query), and used curl and jq to get the data in the shell script.<\/p>\n<p>First, I have defined the service in the database:<\/p>\n<pre class=\"lang:plsql decode:true\">BEGIN\r\n  ORDS.DEFINE_SERVICE(\r\n    p_module_name    =&gt; 'ohctl',\r\n    p_base_path      =&gt; 'ohctl\/',\r\n    p_pattern        =&gt; 'list\/',\r\n    p_method         =&gt; 'GET',\r\n    p_source_type    =&gt; ORDS.source_type_collection_feed,\r\n    p_source         =&gt; 'SELECT name, version, fullpath, TO_CHAR(created,''YYYY-MM-DD'') as created FROM oh_golden_images WHERE oh_type=''RDBMS'' order by created',\r\n    p_items_per_page =&gt; 0);\r\n\tCOMMIT;\r\nEND;\r\n\/<\/pre>\n<p>At this point, a direct call gives this:<\/p>\n<pre class=\"wrap:true lang:sh decode:true\">$ curl $rest_ep\/ohctl\/list\/\r\n{\"items\":[{\"name\":\"18_3_0_cerndb1\",\"version\":\"18.3.0\",\"fullpath\":\"\/test\/path\/18_3_0_cerndb1.zip\",\"created\":\"2018-08-19\"},{\"name\":\"18_3_0_cerndb2\",\"version\":\"18.3.0\",\"fullpath\":\"\/test\/path\/18_3_0_cerndb2.zip\",\"created\":\"2018-08-28\"}],\"hasMore\":false,\"limit\":0,\"offset\":0,\"count\":2,\"links\":[{\"rel\":\"self\",\"href\":\"https:\/\/rest_endpoint\/ohctl\/list\/\"},{\"rel\":\"describedby\",\"href\":\"https:\/\/rest_endpoint\/metadata-catalog\/ohctl\/list\/\"}]}\r\n<\/pre>\n<p>How to parse the data?<\/p>\n<p><strong>jq<\/strong> is a command-line JSON processor that can be used in a pipeline.<\/p>\n<p>I can get the items:<\/p>\n<pre class=\"lang:sh decode:true\">$ curl -s $rest_ep\/ohctl\/list\/ | jq --raw-output  '.items[]'\r\n{\r\n  \"created\": \"2018-08-19\",\r\n  \"fullpath\": \"\/test\/path\/18_3_0_cerndb1.zip\",\r\n  \"version\": \"18.3.0\",\r\n  \"name\": \"18_3_0_cerndb1\"\r\n}\r\n{\r\n  \"created\": \"2018-08-28\",\r\n  \"fullpath\": \"\/test\/path\/18_3_0_cerndb2.zip\",\r\n  \"version\": \"18.3.0\",\r\n  \"name\": \"18_3_0_cerndb2\"\r\n}<\/pre>\n<p>And I can produce a csv output:<\/p>\n<pre class=\"lang:sh decode:true \">$ curl -s $rest_ep\/ohctl\/list\/ | jq --raw-output  '.items[] | @csv \"\\([.created]),\\([.fullpath]),\\([.version]),\\([.name])\"'\r\n\"2018-08-19\",\"\/test\/path\/18_3_0_cerndb1.zip\",\"18.3.0\",\"18_3_0_cerndb1\"\r\n\"2018-08-28\",\"\/test\/path\/18_3_0_cerndb2.zip\",\"18.3.0\",\"18_3_0_cerndb2\"\r\n<\/pre>\n<p>But the best, is the shell formatter, that returns strings properly escaped for usage in shell commands:<\/p>\n<pre class=\"lang:sh decode:true\">$ curl -s $rest_ep\/ohctl\/list\/ | jq --raw-output  '.items[] | @sh \"L_GI_Date=\\([.created]); L_GI_Path=\\([.fullpath]); L_GI_Version=\\([.version]); L_GI_Name=\\([.name])\"'\r\nL_GI_Date='2018-08-19'; L_GI_Path='\/test\/path\/18_3_0_cerndb1.zip'; L_GI_Version='18.3.0'; L_GI_Name='18_3_0_cerndb1'\r\nL_GI_Date='2018-08-28'; L_GI_Path='\/test\/path\/18_3_0_cerndb2.zip'; L_GI_Version='18.3.0'; L_GI_Name='18_3_0_cerndb2'\r\n<\/pre>\n<p>At this point, the call to eval is a natural step \ud83d\ude42<\/p>\n<pre class=\"lang:sh decode:true \">IFS=\"\r\n\"\r\nfor line in `curl -s $rest_ep\/ohctl\/list\/ | jq --raw-output  '.items[] | @sh \"L_GI_Date=\\([.created]); L_GI_Path=\\([.fullpath]); L_GI_Version=\\([.version]); L_GI_Name=\\([.name])\"'` ; do\r\n        eval $line\r\n        echo \"doing something with values $L_GI_Name $L_GI_Date $L_GI_Path $L_GI_Version\"\r\ndone\r\n<\/pre>\n<p>The output:<\/p>\n<pre class=\"lang:sh highlight:0 decode:true \">$ sh ords_test.sh\r\ndoing something with values 18_3_0_cerndb1 2018-08-19 \/test\/path\/18_3_0_cerndb1.zip 18.3.0\r\ndoing something with values 18_3_0_cerndb2 2018-08-28 \/test\/path\/18_3_0_cerndb2.zip 18.3.0\r\n<\/pre>\n<p>\ud83d\ude09<\/p>\n<p>&#8212;<\/p>\n<p>Ludovico<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I develop a lot of shell scripts. I would not define myself an old dinosaur that keeps avoiding python or other modern languages. It is just that most of my scripts automate OS commands that I would normally run interactively &hellip; <a href=\"https:\/\/www.ludovicocaldara.net\/dba\/bash-sqlplus-to-ords\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[321,336,326,3,331],"tags":[],"class_list":["post-1803","post","type-post","status-publish","format-standard","hentry","category-aced","category-devops","category-oracle","category-oracledb","category-web"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts\/1803","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/comments?post=1803"}],"version-history":[{"count":6,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts\/1803\/revisions"}],"predecessor-version":[{"id":1810,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/posts\/1803\/revisions\/1810"}],"wp:attachment":[{"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/media?parent=1803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/categories?post=1803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ludovicocaldara.net\/dba\/wp-json\/wp\/v2\/tags?post=1803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}