<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.23">
<meta name="author" content="Perforce Professional Services">
<title>Coding Standard for Bash Scripts</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
<style>
/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
/* Uncomment the following line when using as a custom stylesheet */
/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
html{font-family:sans-serif;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
b,strong{font-weight:bold}
abbr{font-size:.9em}
abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
dfn{font-style:italic}
hr{height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type=checkbox],input[type=radio]{padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,::before,::after{box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ul.square{list-style-type:square}
ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt{background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
:not(pre).nobreak{word-wrap:normal}
:not(pre).nowrap{white-space:nowrap}
:not(pre).pre-wrap{white-space:pre-wrap}
:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
pre>code{display:block}
pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
details{margin-left:1.25rem}
details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
details>summary::-webkit-details-marker{display:none}
details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.prettyprint{background:#f7f7f8}
pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
table.linenotable td.code{padding-left:.75em}
table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
pre.pygments span.linenos{display:inline-block;margin-right:.75em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
td.tableblock>.content>:last-child{margin-bottom:-1.25em}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>*>tr>*{border-width:1px}
table.grid-cols>*>tr>*{border-width:0 1px}
table.grid-rows>*>tr>*{border-width:1px 0}
table.frame-all{border-width:1px}
table.frame-ends{border-width:1px 0}
table.frame-sides{border-width:0 1px}
table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
li>p:empty:only-child::before{content:"";display:inline-block}
ul.checklist>li>p:first-child{margin-left:-1em}
ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
td.hdlist2{word-wrap:anywhere}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background:#00fafa}
.black{color:#000}
.black-background{background:#000}
.blue{color:#0000bf}
.blue-background{background:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background:#fa00fa}
.gray{color:#606060}
.gray-background{background:#7d7d7d}
.green{color:#006000}
.green-background{background:#007d00}
.lime{color:#00bf00}
.lime-background{background:#00fa00}
.maroon{color:#600000}
.maroon-background{background:#7d0000}
.navy{color:#000060}
.navy-background{background:#00007d}
.olive{color:#606000}
.olive-background{background:#7d7d00}
.purple{color:#600060}
.purple-background{background:#7d007d}
.red{color:#bf0000}
.red-background{background:#fa0000}
.silver{color:#909090}
.silver-background{background:#bcbcbc}
.teal{color:#006060}
.teal-background{background:#007d7d}
.white{color:#bfbfbf}
.white-background{background:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]{border-bottom:1px dotted}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#header,#content,#footnotes,#footer{max-width:none}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body class="book">
<div id="header">
<h1>Coding Standard for Bash Scripts</h1>
<div class="details">
<span id="author" class="author">Perforce Professional Services</span><br>
<span id="email" class="email"><a href="mailto:[email protected]">[email protected]</a></span><br>
<span id="revnumber">version v2025.2,</span>
<span id="revdate">2026-04-07</span>
</div>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_draft_notice">DRAFT NOTICE</a></li>
<li><a href="#_preface">Preface</a></li>
<li><a href="#_bash_version">1. Bash Version</a></li>
<li><a href="#_bash_directives">2. Bash Directives</a></li>
<li><a href="#_scripts_and_libraries">3. Scripts and Libraries</a></li>
<li><a href="#_script_templates">4. Script Templates</a></li>
<li><a href="#_script_sections">5. Script Sections</a>
<ul class="sectlevel2">
<li><a href="#_header">5.1. Header</a></li>
<li><a href="#_declarations_and_environment">5.2. Declarations and Environment</a></li>
<li><a href="#_local_functions">5.3. Local Functions</a></li>
<li><a href="#_sdp_library_functions">5.4. SDP Library Functions</a></li>
<li><a href="#_command_line_processing">5.5. Command Line Processing</a></li>
<li><a href="#_command_line_verification">5.6. Command Line Verification</a></li>
<li><a href="#_main_program">5.7. Main Program</a></li>
</ul>
</li>
<li><a href="#_sdp_root_and_relocatability">6. SDP Root and Relocatability</a></li>
<li><a href="#_version_identification">7. Version Identification</a>
<ul class="sectlevel2">
<li><a href="#_location">7.1. Location</a></li>
</ul>
</li>
<li><a href="#_logging">8. Logging</a>
<ul class="sectlevel2">
<li><a href="#_log_file_location_and_naming">8.1. Log File Location and Naming</a></li>
<li><a href="#_log_symlink_loglink">8.2. Log Symlink (LogLink)</a></li>
<li><a href="#_log_redirection">8.3. Log Redirection</a></li>
<li><a href="#_controlling_the_log">8.4. Controlling the Log</a></li>
</ul>
</li>
<li><a href="#_standards">9. Standards</a>
<ul class="sectlevel2">
<li><a href="#_tenets_of_scripting">9.1. Tenets of Scripting</a>
<ul class="sectlevel3">
<li><a href="#_avoid_requiring_modification">9.1.1. Avoid Requiring Modification</a></li>
<li><a href="#_self_logging">9.1.2. Self Logging</a></li>
<li><a href="#_error_handling">9.1.3. Error Handling</a></li>
</ul>
</li>
<li><a href="#_style">9.2. Style</a>
<ul class="sectlevel3">
<li><a href="#_indentation">9.2.1. Indentation</a></li>
<li><a href="#_naming_conventions">9.2.2. Naming Conventions</a>
<ul class="sectlevel4">
<li><a href="#_shell_environment_variables">9.2.2.1. Shell Environment Variables</a></li>
<li><a href="#_global_script_variables">9.2.2.2. Global Script Variables</a></li>
<li><a href="#_library_output_variables">9.2.2.3. Library Output Variables</a></li>
<li><a href="#_summary_of_naming_conventions">9.2.2.4. Summary of Naming Conventions</a></li>
<li><a href="#_function_scoped_local_variables">9.2.2.5. Function-scoped (Local) Variables</a></li>
<li><a href="#_function_names">9.2.2.6. Function Names</a></li>
</ul>
</li>
<li><a href="#_quoting">9.2.3. Quoting</a></li>
<li><a href="#_conditionals">9.2.4. Conditionals</a></li>
<li><a href="#_line_length">9.2.5. Line Length</a></li>
<li><a href="#_continuation_lines">9.2.6. Continuation Lines</a></li>
<li><a href="#_comments">9.2.7. Comments</a></li>
</ul>
</li>
<li><a href="#_shellcheck_compliance">9.3. ShellCheck Compliance</a></li>
</ul>
</li>
<li><a href="#_shellcheck_appendix">Appendix A: ShellCheck Notes</a></li>
<li><a href="#_draft_notice_2">Appendix B: DRAFT NOTICE</a></li>
</ul>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="_draft_notice">DRAFT NOTICE</h2>
<div class="sectionbody">
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
This document is in DRAFT status and should not be relied on yet. It is a preview of a document to be completed in a future release.
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_preface">Preface</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Welcome to the Perforce P4 Server Deployment Package (SDP) Coding Standard for Bash scripts.</p>
</div>
<div class="paragraph">
<p>This Standard is intended to provide information useful for Bash script programming in the SDP Environment. This applies to scripts delivered as part of the SDP Package, and can also be applied to custom scripts such as custom triggers or systems integrations for interaction with P4 Servers that are added to the SDP in any given environment in <a href="../Server/Unix/p4/common/site/ReadMe.md">The Site Directory</a>.</p>
</div>
<div class="paragraph">
<p>This document provides both <em>standards</em> and <em>guidelines</em> to follow. Standards must be followed for a script to be considered adherent to this standard. Guidelines are suggestions but are not strictly required to achieve compliance. Generally descriptions involving clear words like <em>must</em> indicate standards, while terms like <em>should</em> that allow for variation indicate guidelines.</p>
</div>
<div class="paragraph">
<p><strong>Please Give Us Feedback</strong></p>
</div>
<div class="paragraph">
<p>Perforce welcomes feedback. Please send suggestions for improving this document or the SDP to <a href="mailto:[email protected]">[email protected]</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_bash_version">1. Bash Version</h2>
<div class="sectionbody">
<div class="paragraph">
<p>SDP may assume the bash version to be 4.3+ for purposes of deciding which features to use. For example, features like associative arrays can be assumed, as well as syntax like <code>${var,,}</code> to return the lowercase form of the value of $var.</p>
</div>
<div class="paragraph">
<p>The <em>shebang</em> line (the first line of each bash script that starts with the <code>#!</code> sequence) must be one of these two options:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>#!/bin/bash</pre>
</div>
</div>
<div class="paragraph">
<p>or</p>
</div>
<div class="literalblock">
<div class="content">
<pre>#!/usr/bin/env bash</pre>
</div>
</div>
<div class="paragraph">
<p>While some scripts may run with Bash 3.x, others require Bash 4.x or later, as this is reliably available on all Linux distributions that are not End of Life (EOL). For example, Bash 4+ was introduced in the Red Hat family distributions starting in version 7, in Ubuntu starting with Ubuntu 10.04, and SuSE Enterprise Linux 12.</p>
</div>
<div class="paragraph">
<p>As a whole, the SDP bash scripts support any version of UNIX/Linux that provides bash 4.0 or later. This includes all Linux and most UNIX environments except for OSX (for which the workaround is installing modern bash and adjusting shebang lines).</p>
</div>
<div class="paragraph">
<p>This standard applies to SDP on UNIX/Linux. For SDP on Windows, PowerShell, Python and Batch (<code>.bat</code>) scripts are used, so this standard for Bash does not apply. (Bash can run on Windows in various ways and some scripts may work, but are not supported due to not being tested.)</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_bash_directives">2. Bash Directives</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Immediately after the shebang line should appear the <code>set -u</code> directive, requiring variables to be defined before being referenced. The <code>set +u</code> directive is allowed as needed for examples, such as in command line processing where variables like <code>$1</code> may legitimately be referenced while undefined.</p>
</div>
<div class="paragraph">
<p>SDP scripts do not use <code>set -e</code>. Explicit error handling at each point where errors can occur is preferred instead. See the Error Handling tenet in the Standards section.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_scripts_and_libraries">3. Scripts and Libraries</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A <em>script</em> is a bash shell script file intended to be executed directly by users or other automation, and for which the first line is the shebang line. Scripts have the <code>+x</code> execute bit set.</p>
</div>
<div class="paragraph">
<p>A <em>library</em> is a bash shell file intended to be sourced by scripts or other library functions. Library files have a <code>.lib</code> suffix, and do not have the <code>+x</code> execute bit set. Library files generally define reusable functions and may contribute to the shell environment.</p>
</div>
<div class="paragraph">
<p>Scripts appear in various directories in the deployed SDP structure.</p>
</div>
<div class="paragraph">
<p>Libraries should appear only in the <code><SDPRoot>/common/lib</code> directory for scripts that are part of the SDP package, otherwise <code><SDPRoot>/common/site/lib</code> for site-specific libraries.</p>
</div>
<div class="paragraph">
<p>Legacy exceptions: The <code>backup_functions.sh</code> remains in the <code><SDPRoot>/common/bin</code> directory for backward compatibility with customer-side custom scripts. Some older library files may retain the <code>.sh</code> rather than <code>.lib</code> suffix.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_script_templates">4. Script Templates</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The bash script template illustrates and adheres to this standard. Here is the link to <a href="https://workshop.perforce.com/view/p4-sdp/main/Server/Unix/p4/common/bin/templates/template.sh">the bash shell script template</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_script_sections">5. Script Sections</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Scripts should have whichever of the following sections apply:</p>
</div>
<div class="sect2">
<h3 id="_header">5.1. Header</h3>
<div class="paragraph">
<p>The Header section contains the shebang line, directives, license info, and
indication of how to get documentation for the script.</p>
</div>
<div class="listingblock">
<div class="title">Section Example - Header</div>
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">#!/bin/bash
set -u
#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
#------------------------------------------------------------------------------</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_declarations_and_environment">5.2. Declarations and Environment</h3>
<div class="paragraph">
<p>This section declares global variables. Creating the shell environment for the script also starts in this section.</p>
</div>
<div class="listingblock">
<div class="title">Section Example - Declarations and Environment</div>
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">#==============================================================================
# Declarations and Environment
declare -i ErrorCount=0</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_local_functions">5.3. Local Functions</h3>
<div class="paragraph">
<p>Each script should define a subset of these standard local functions:</p>
</div>
<div class="listingblock">
<div class="title">Section Example - Local Functions</div>
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_sdp_library_functions">5.4. SDP Library Functions</h3>
<div class="paragraph">
<p>Most scripts will use SDP libraries. Each library is sourced using the <code>$SDPCommonLib</code> variable.</p>
</div>
<div class="listingblock">
<div class="title">Section Example - SDP Library Functions</div>
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">#==============================================================================
# Load SDP Library Functions.
if [[ -d "$SDPCommonLib" ]]; then
# shellcheck disable=SC1090 disable=SC1091
source "$SDPCommonLib/logging.lib" ||\
bail "Failed to load bash lib [$SDPCommonLib/logging.lib]. Aborting."
# shellcheck disable=SC1090 disable=SC1091
source "$SDPCommonLib/run.lib" ||\
bail "Failed to load bash lib [$SDPCommonLib/run.lib]. Aborting."
fi</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_command_line_processing">5.5. Command Line Processing</h3>
<div class="paragraph">
<p>See the script template for an example of the Command Line Processing block.</p>
</div>
</div>
<div class="sect2">
<h3 id="_command_line_verification">5.6. Command Line Verification</h3>
<div class="paragraph">
<p>See the script template for an example of the Command Line Verification block.</p>
</div>
</div>
<div class="sect2">
<h3 id="_main_program">5.7. Main Program</h3>
<div class="paragraph">
<p>See the script template for an example of the Main Program block. Among other things, this section is responsible for starting any log file processing that is to be done.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_sdp_root_and_relocatability">6. SDP Root and Relocatability</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In Production, the SDP Root Directory (referenced as the <code>$SDP_ROOT</code> shell environment variable or the <code>$SDPRoot</code> variable in scripts) will always have a value of <code>/p4</code>. However, the standard allows this root to be changed to simplify testing and development. All scripts should behave properly if this value is changed. In any production environment, the default value should apply; a value for SDP_ROOT should only be set for dev/test environments.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_version_identification">7. Version Identification</h2>
<div class="sectionbody">
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
This section does not yet apply to the released SDP; it is forward-looking. This requires the SDP to complete its migration from a Classic depot structure to its new Streams structure for versioning to work as described.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>All executable shell scripts and libraries must include a <em>Version ID Block</em>.</p>
</div>
<div class="paragraph">
<p>The regular form of the version ID block looks like this:</p>
</div>
<div class="listingblock">
<div class="title">Version ID Block (Regular)</div>
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash"># Version ID Block. Relies on +k filetype modifier.
#------------------------------------------------------------------------------
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/dev_rebrand/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh#6 $ $Change: 31803 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"</code></pre>
</div>
</div>
<div class="paragraph">
<p>This Version ID Block has several features:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>It ensures file versions are updated reliably on every submit by taking advantage of the P4 <code>+k</code> file type modifier to expand keywords in the file upon submit.</p>
</li>
<li>
<p>The SDP major version is determined from the stream name (e.g., r25.2), so the version clarifies what released SDP version the file is part of.</p>
</li>
<li>
<p>Non-released "dev branch" versions will have a version identifier that clearly indicates they are not released production code.</p>
</li>
<li>
<p>The changelist number gives each file a unique identifier.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>There is also a short form of this block intended for library files:</p>
</div>
<div class="listingblock">
<div class="title">Version ID Block (Short Form)</div>
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash"># Version ID Block. Relies on +k filetype modifier.
#------------------------------------------------------------------------------
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/dev_rebrand/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh#6 $ $Change: 31803 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"</code></pre>
</div>
</div>
<div class="sect2">
<h3 id="_location">7.1. Location</h3>
<div class="paragraph">
<p>Supported SDP scripts provided by Perforce appear in <code>/p4/common/bin</code>.</p>
</div>
<div class="paragraph">
<p>Custom scripts (inherently unsupported) are expected to appear somewhere under the <code>/p4/common/site</code> directory, such as <code>/p4/common/site/bin</code> or <code>/p4/common/site/p4ms</code>.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_logging">8. Logging</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Scripts must be self-logging: all output generated during execution (stdout
and stderr) is automatically captured in a log file without requiring the
caller to use redirection or <code>tee</code>.</p>
</div>
<div class="sect2">
<h3 id="_log_file_location_and_naming">8.1. Log File Location and Naming</h3>
<div class="paragraph">
<p>Log files are written to the <code>$LOGS</code> directory, which is set by <code>p4_vars</code> to
<code>/p4/<instance>/logs</code>. Each script run produces a time-stamped log file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>$LOGS/<ScriptName>.<YYYY-MM-DD-HHMMSS>.log</pre>
</div>
</div>
<div class="paragraph">
<p>For example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>/p4/1/logs/daily_checkpoint.2026-04-07-143022.log</pre>
</div>
</div>
<div class="paragraph">
<p>If two invocations start within the same second (e.g., a cron job and a manual
run), an incrementing integer suffix is appended to guarantee uniqueness:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>$LOGS/<ScriptName>.<YYYY-MM-DD-HHMMSS>.<N>.log</pre>
</div>
</div>
<div class="paragraph">
<p>Log files are created atomically using the bash <code>noclobber</code> option (<code>set -C</code>),
ensuring that concurrent invocations never claim the same filename.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
Millisecond precision (<code>%3N</code>) is intentionally not used in fallback
filenames because it is a GNU <code>date</code> extension not available on macOS. The
integer suffix is sufficient to guarantee uniqueness and is fully portable.
</td>
</tr>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_log_symlink_loglink">8.2. Log Symlink (LogLink)</h3>
<div class="paragraph">
<p>In addition to the time-stamped log file, each script maintains a stable
symlink in the same directory:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>$LOGS/<ScriptName>.log → $LOGS/<ScriptName>.<timestamp>.log</pre>
</div>
</div>
<div class="paragraph">
<p>This <code>LogLink</code> symlink always points to the most recently started log,
providing a predictable name for operators running <code>tail -f</code>, monitoring
tools, and any integrations that need to locate the latest log without knowing
the exact timestamp.</p>
</div>
<div class="paragraph">
<p>If a regular file already exists at the <code>LogLink</code> path (e.g., from an older
SDP version that did not use symlinks), it is automatically renamed to a
time-stamped filename before the symlink is created.</p>
</div>
</div>
<div class="sect2">
<h3 id="_log_redirection">8.3. Log Redirection</h3>
<div class="paragraph">
<p>After the log file and symlink are established, both stdout and stderr are
redirected to the log via <code>exec</code>.</p>
</div>
<div class="paragraph">
<p>When running interactively (terminal attached, color mode active), a <code>tee</code>
process substitution is used so that output appears on both the terminal and
in the log simultaneously. ANSI color codes are stripped from the log copy
so that log files remain clean when viewed with standard text tools.</p>
</div>
<div class="paragraph">
<p>In silent mode (<code>-si</code>), all output goes to the log only — nothing appears on
the terminal. This is the intended mode for crontab invocations, where any
terminal output would trigger an email from the cron daemon.</p>
</div>
</div>
<div class="sect2">
<h3 id="_controlling_the_log">8.4. Controlling the Log</h3>
<div class="paragraph">
<p>Two command-line options govern logging behavior:</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>-L <file></code></dt>
<dd>
<p>Write the log to <code><file></code> instead of the default time-stamped
file in <code>$LOGS</code>. No <code>LogLink</code> symlink is created in this case.</p>
</dd>
<dt class="hdlist1"><code>-L off</code></dt>
<dd>
<p>Disable logging entirely. All output goes to the terminal only.
Cannot be combined with <code>-si</code>.</p>
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_standards">9. Standards</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_tenets_of_scripting">9.1. Tenets of Scripting</h3>
<div class="sect3">
<h4 id="_avoid_requiring_modification">9.1.1. Avoid Requiring Modification</h4>
<div class="paragraph">
<p>No modification of scripts should be needed in customer environments for normal operation. An appropriate mix of command-line options and configuration files should be used to provide the flexibility required to operate to meet various customer needs.</p>
</div>
</div>
<div class="sect3">
<h4 id="_self_logging">9.1.2. Self Logging</h4>
<div class="paragraph">
<p>Scripts must be self-logging: all output (stdout and stderr) is captured in a
log file automatically, with no need for the caller to use redirection or
<code>tee</code>. See <a href="#_logging">Chapter 8, <em>Logging</em></a>.</p>
</div>
<div class="paragraph">
<p>All scripts should support <code>-h</code> (short usage synopsis), <code>-man</code> (full
documentation), and <code>-V</code> (version check, with <code>--version</code> alias) options,
with standard meanings.
All scripts must define a <code>usage()</code> function per the template. All scripts
must have a <code>terminate()</code> function available; this is provided by sourcing
<code>logging.lib</code> rather than defined locally in each script.
All scripts should have complete documentation.</p>
</div>
</div>
<div class="sect3">
<h4 id="_error_handling">9.1.3. Error Handling</h4>
<div class="paragraph">
<p>SDP scripts do not use <code>set -e</code>. Instead, errors are handled explicitly at
each point where they can occur, using one of three mechanisms:</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>bail</code></dt>
<dd>
<p>Fatal error. Prints a red error message, increments <code>ErrorCount</code>,
and exits immediately. Use this when continuing is not meaningful.</p>
</dd>
</dl>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">some_command || bail "some_command failed; cannot continue."</code></pre>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>errmsg</code></dt>
<dd>
<p>Non-fatal error. Prints a red error message and increments
<code>ErrorCount</code>, but execution continues. Use this when the script should keep
running and report a failure count at the end.</p>
</dd>
</dl>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">some_command || errmsg "some_command failed; continuing."</code></pre>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>warnmsg</code></dt>
<dd>
<p>Warning. Prints a yellow warning message and increments
<code>WarningCount</code>. Use this for conditions that are noteworthy but not errors.</p>
</dd>
</dl>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">[[ -n "$SomeOptionalVar" ]] || warnmsg "SomeOptionalVar is not set."</code></pre>
</div>
</div>
<div class="paragraph">
<p>At the end of the script, <code>ErrorCount</code> and <code>WarningCount</code> are checked to
produce a final summary message and to set the exit code. The script exits
with a value of <code>$ErrorCount</code>, so non-fatal errors still result in a non-zero
exit code. The <code>terminate()</code> function (from <code>logging.lib</code>) handles this
final exit.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_style">9.2. Style</h3>
<div class="sect3">
<h4 id="_indentation">9.2.1. Indentation</h4>
<div class="paragraph">
<p>Scripts must use 3-space indentation. Tab characters must not appear in
script or library files, except within here-documents where tab characters
carry semantic meaning.</p>
</div>
</div>
<div class="sect3">
<h4 id="_naming_conventions">9.2.2. Naming Conventions</h4>
<div class="sect4">
<h5 id="_shell_environment_variables">9.2.2.1. Shell Environment Variables</h5>
<div class="paragraph">
<p>Shell environment variables defined outside scripts — set in the surrounding
shell environment or in SDP environment files such as <code>p4_vars</code> — must be
all uppercase with underscore word separators, following standard UNIX/POSIX
conventions. Examples: <code>SDP_ROOT</code>, <code>P4PORT</code>, <code>LOGS</code>.</p>
</div>
</div>
<div class="sect4">
<h5 id="_global_script_variables">9.2.2.2. Global Script Variables</h5>
<div class="paragraph">
<p>Variables with global scope (declared outside any function) must use
UpperCamelCase (PascalCase), starting with an uppercase letter.
Examples: <code>ThisScript</code>, <code>ErrorCount</code>, <code>LogTimestamp</code>.</p>
</div>
<div class="paragraph">
<p>Constants — variables assigned once and not expected to change during the
lifetime of the script — may alternatively use all-uppercase naming with
underscore separators. Examples: <code>H1</code>, <code>H2</code>, <code>GREEN</code>, <code>RESET</code>.</p>
</div>
</div>
<div class="sect4">
<h5 id="_library_output_variables">9.2.2.3. Library Output Variables</h5>
<div class="paragraph">
<p>Variables that are written by library functions and read by calling scripts
must use <code>ALL_UPPERCASE</code> with underscore separators. This signals that the
variable crosses an ownership boundary: the calling script did not set it —
the library did. Examples: <code>CMDLAST</code>, <code>CMDEXITCODE</code>, <code>RCMDLAST</code>, <code>RCMDEXITCODE</code>.</p>
</div>
<div class="paragraph">
<p>This mirrors the convention for shell environment variables (which are also set
outside the script) and gives a script reader an immediate visual cue: an
all-uppercase name not listed in the script’s own Declarations section is a
library output, not a local variable.</p>
</div>
</div>
<div class="sect4">
<h5 id="_summary_of_naming_conventions">9.2.2.4. Summary of Naming Conventions</h5>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Scope / Origin</th>
<th class="tableblock halign-left valign-top">Convention</th>
<th class="tableblock halign-left valign-top">Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Shell environment (set outside the script)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>ALL_UPPERCASE</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SDP_ROOT</code>, <code>P4PORT</code>, <code>LOGS</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Global script variable (set by the script)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>UpperCamelCase</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>ThisScript</code>, <code>ErrorCount</code>, <code>LogTimestamp</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Constant (set once, does not change)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>ALL_UPPERCASE</code> or <code>UpperCamelCase</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>H1</code>, <code>H2</code>, <code>GREEN</code>, <code>RESET</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Library output variable (set by a library, read by the script)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>ALL_UPPERCASE</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>CMDLAST</code>, <code>CMDEXITCODE</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Function-local variable</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>lowerCamelCase</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>cmd</code>, <code>honorNoOpFlag</code>, <code>cmdOut</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Function name</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>lowercase</code> or <code>lower_with_underscores</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>msg</code>, <code>bail</code>, <code>get_old_log_timestamp</code></p></td>
</tr>
</tbody>
</table>
</div>
<div class="sect4">
<h5 id="_function_scoped_local_variables">9.2.2.5. Function-scoped (Local) Variables</h5>
<div class="paragraph">
<p>Variables declared inside functions must use the <code>local</code> keyword and must
use lowerCamelCase, starting with a lowercase letter.
Examples: <code>cmd</code>, <code>desc</code>, <code>honorNoOpFlag</code>, <code>cmdOut</code>.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<code>declare</code> inside a function is functionally equivalent to <code>local</code> in
Bash, but <code>local</code> is preferred for function-scoped variables because it more
clearly expresses intent.
</td>
</tr>
</table>
</div>
</div>
<div class="sect4">
<h5 id="_function_names">9.2.2.6. Function Names</h5>
<div class="paragraph">
<p>Function names must be all lowercase. Multi-word function names use
underscore separators.
Examples: <code>msg</code>, <code>errmsg</code>, <code>bail</code>, <code>get_old_log_timestamp</code>.</p>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_quoting">9.2.3. Quoting</h4>
<div class="paragraph">
<p>All variable expansions must be double-quoted unless word splitting or glob
expansion is intentionally required. Use <code>"$var"</code> rather than <code>$var</code>.</p>
</div>
</div>
<div class="sect3">
<h4 id="_conditionals">9.2.4. Conditionals</h4>
<div class="paragraph">
<p>Use <code>[[ ]]</code> (double brackets) rather than <code>[ ]</code> (single brackets) for all
conditional tests. Double brackets are a Bash built-in with cleaner behavior
for string comparisons, pattern matching, and avoidance of word-splitting
surprises.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash"># Correct
[[ -n "$MyVar" ]]
[[ "$Count" -gt 0 ]]
# Avoid
[ -n "$MyVar" ]
[ "$Count" -gt 0 ]</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_line_length">9.2.5. Line Length</h4>
<div class="paragraph">
<p>Lines should not exceed 120 characters. Keeping lines to 80 characters is
preferred where practical, especially for documentation-heavy comment blocks.</p>
</div>
</div>
<div class="sect3">
<h4 id="_continuation_lines">9.2.6. Continuation Lines</h4>
<div class="paragraph">
<p>When a command spans multiple lines, use <code>\</code> for line continuation and indent
the continuation line by 3 additional spaces relative to the opening of the
command.</p>
</div>
</div>
<div class="sect3">
<h4 id="_comments">9.2.7. Comments</h4>
<div class="paragraph">
<p>Use <code>#</code> comments to explain intent, especially for logic that is not
immediately apparent from the code. Inline comments are separated from code
by at least two spaces.</p>
</div>
<div class="paragraph">
<p>Section headings use a standard major divider:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>#==============================================================================
# Section Name</pre>
</div>
</div>
<div class="paragraph">
<p>Function headers and sub-section breaks use a minor divider:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>#------------------------------------------------------------------------------
# Function: function_name
#
# Short description of what the function does.
#
# Input:
# $1 - first_param: Description.
# $2 - second_param: Description.
#
# Returns: 0 on success, non-zero on error.
#------------------------------------------------------------------------------</pre>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_shellcheck_compliance">9.3. ShellCheck Compliance</h3>
<div class="paragraph">
<p>The ShellCheck utility is a static code analysis tool for bash shell scripts. See: <a href="https://www.shellcheck.net" class="bare">https://www.shellcheck.net</a></p>
</div>
<div class="paragraph">
<p>All SDP scripts and library files must pass a ShellCheck scan with ShellCheck version 0.10.0 or later.</p>
</div>
<div class="paragraph">
<p>Where appropriate, <code>#shellcheck disable=SC<NNNN></code> directives may be used, as may <code>.shellcheckrc</code> files, to suppress warnings deemed not of concern.
See <a href="#_shellcheck_appendix">Appendix A, <em>ShellCheck Notes</em></a> for cases where ShellCheck guidance conflicts with SDP style.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_shellcheck_appendix">Appendix A: ShellCheck Notes</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This appendix documents cases where ShellCheck guidance conflicts with SDP
style, and the rationale for the SDP’s position.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_draft_notice_2">Appendix B: DRAFT NOTICE</h2>
<div class="sectionbody">
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
This document is in DRAFT status and should not be relied on yet. It is a preview of a document to be completed in a future release.
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Version v2025.2<br>
Last updated 2026-04-07 23:38:40 -0400
</div>
</div>
</body>
</html>