<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Bash</title>
	<atom:link href="https://blog.gmem.cc/tag/bash/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Bash学习笔记</title>
		<link>https://blog.gmem.cc/bash-study-note</link>
		<comments>https://blog.gmem.cc/bash-study-note#comments</comments>
		<pubDate>Sat, 15 Mar 2008 02:52:39 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[Command]]></category>
		<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[文本处理]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2298</guid>
		<description><![CDATA[<p>Bash简介 在Linux系统中，作为/bin/sh的标准Shell是GNU工具集中的bash（GNU Bourne-Again Shell），大多数Linux发行版中/bin/sh是指向/bin/bash的一个链接。 单实例运行 基于PID进行控制 Bash编程中，可以通过PID文件进行简单的单例控制——仅仅同时仅仅允许脚本的单个实例在运行： [crayon-69de84d464127324745568/] 使用flock命令 [crayon-69de84d464130911668812/] 色彩控制 我们可以对终端输出的字符颜色、背景色等参数进行设置，实现方式是转义序列。这个控制实际上与Bash语言无关，其它语言在输出时也可以使用。 转义序列以ESC字符开头，对应八进制的\033。控制代码格式为： [crayon-69de84d464133472554474/] 注意：显示方式、前景色、背景色都是可选的，而且顺序可以颠倒。示例： [crayon-69de84d464137872902708/] 在脚本中使用色彩控制的例子： [crayon-69de84d46413a736305964/] 编码列表 编码 动作 &#124; 颜色 <a class="read-more" href="https://blog.gmem.cc/bash-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/bash-study-note">Bash学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Bash简介</span></div>
<p>在Linux系统中，作为/bin/sh的标准Shell是GNU工具集中的bash（GNU Bourne-Again Shell），大多数Linux发行版中/bin/sh是指向/bin/bash的一个链接。</p>
<div class="blog_h1"><span class="graybg">单实例运行</span></div>
<div class="blog_h2"><span class="graybg">基于PID进行控制</span></div>
<p>Bash编程中，可以通过PID文件进行简单的单例控制——仅仅同时仅仅允许脚本的单个实例在运行：</p>
<pre class="crayon-plain-tag">#!/usr/bin/env bash
PIDFILE=/var/run/gmem-sync.pid
if [ -f $PIDFILE ]
then
  # PID文件存在
  PID=$(cat $PIDFILE)
  ps -p $PID &gt; /dev/null 2&gt;&amp;1
  if [ $? -eq 0 ]
  then
    # PID文件指定的进程存在
    echo "Job is already running"
    exit 1
  else
    # PID文件指定的进程不存在
    echo $$ &gt; $PIDFILE
    if [ $? -ne 0 ]
    then
      echo "Could not create PID file"
      exit 1
    fi
  fi
else
  echo $$ &gt; $PIDFILE
  if [ $? -ne 0 ]
  then
    echo "Could not create PID file"
    exit 1
  fi
fi

# 这里编写主逻辑


# 完毕后删除PID文件
rm $PIDFILE</pre>
<div class="blog_h2"><span class="graybg">使用flock命令</span></div>
<pre class="crayon-plain-tag"># 以独占锁（-x）打开gmem-sync.pid文件，并执行-c指定的命令
# -n 如果无法获得独占锁，立即静默的退出，退出状态非0
flock -xn /var/run/gmem-sync.pid -c /usr/local/bin/gmem-sync.sh</pre>
<div class="blog_h1"><span class="graybg">色彩控制</span></div>
<p>我们可以对终端输出的字符颜色、背景色等参数进行设置，实现方式是转义序列。这个控制实际上与Bash语言无关，其它语言在输出时也可以使用。</p>
<p>转义序列以ESC字符开头，对应八进制的\033。控制代码格式为：</p>
<pre class="crayon-plain-tag">\033[显示方式;前景色;背景色;动作m;</pre>
<p>注意：显示方式、前景色、背景色都是可选的，而且顺序可以颠倒。示例：</p>
<pre class="crayon-plain-tag"># 恢复系统默认设置
\033[0m
# 设置蓝色背景、白色前景、光标闪烁
\033[44;37;5m</pre>
<p>在脚本中使用色彩控制的例子：</p>
<pre class="crayon-plain-tag">echo -e "\033[44mDumping Gmem database on hk.gmem.cc ...\033[0m"</pre>
<div class="blog_h2"><span class="graybg">编码列表</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">编码</td>
<td style="text-align: center;">动作 | 颜色</td>
<td style="text-align: center;">编码</td>
<td style="text-align: center;">动作 | 颜色</td>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>恢复默认设置</td>
<td>36</td>
<td>青色前景</td>
</tr>
<tr>
<td>1</td>
<td>启用粗体</td>
<td>37</td>
<td>白色前景</td>
</tr>
<tr>
<td>2</td>
<td>启用一半亮度</td>
<td>38</td>
<td>默认前景+下划线</td>
</tr>
<tr>
<td>4</td>
<td>启用下划线</td>
<td>39</td>
<td>默认前景+取消下划线</td>
</tr>
<tr>
<td>5</td>
<td>启用闪烁</td>
<td>40</td>
<td>黑色背景</td>
</tr>
<tr>
<td>7</td>
<td>前背景色交换</td>
<td>41</td>
<td>红色背景</td>
</tr>
<tr>
<td>22</td>
<td>禁用粗体</td>
<td>42</td>
<td>绿色背景</td>
</tr>
<tr>
<td>24</td>
<td>禁用下划线</td>
<td>43</td>
<td>棕色背景</td>
</tr>
<tr>
<td>27</td>
<td>禁用前背景色交换</td>
<td>44</td>
<td>蓝色背景</td>
</tr>
<tr>
<td>30</td>
<td>黑色前景</td>
<td>45</td>
<td>紫色背景</td>
</tr>
<tr>
<td>31</td>
<td>红色前景</td>
<td>46</td>
<td>青色背景</td>
</tr>
<tr>
<td>32</td>
<td>绿色前景</td>
<td>47</td>
<td>白色背景</td>
</tr>
<tr>
<td>33</td>
<td>棕色前景</td>
<td>49</td>
<td>黑色背景</td>
</tr>
<tr>
<td>34</td>
<td>蓝色前景</td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>35</td>
<td>紫色前景</td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">系统日志</span></div>
<p>可以利用<a href="http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptfunc.html">初始化脚本函数（Init Script Functions）</a>来输出系统日志：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>log_daemon_msg</td>
<td>输出一般性的daemon日志</td>
</tr>
<tr>
<td>log_end_msg</td>
<td>指定调用参数0：表示成功，1表示失败</td>
</tr>
<tr>
<td>log_success_msg</td>
<td>输出成功消息到未指定的日志文件（unspecified log file）中，消息格式不指定。消息应该相对较短，最好不超过60字符</td>
</tr>
<tr>
<td>log_failure_msg</td>
<td>输出失败消息到未指定的日志文件（unspecified log file）中，消息格式不指定。消息应该相对较短，最好不超过60字符</td>
</tr>
<tr>
<td>log_warning_msg</td>
<td>输出警告消息到未指定的日志文件（unspecified log file）中，消息格式不指定。消息应该相对较短，最好不超过60字符</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">特殊字符</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;">字符 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>#</td>
<td>
<p>单行注释</p>
</td>
</tr>
<tr>
<td>;</td>
<td>
<p>命令分隔符，可以在单行放置多个命令：</p>
<pre class="crayon-plain-tag">echo hello; echo there
if [ -x "$filename" ]; then # 注意分号后面的空格</pre>
</td>
</tr>
<tr>
<td>;;</td>
<td>
<p>CASE子句终止符：
<pre class="crayon-plain-tag">case "$variable" in
    abc) echo "\$variable = abc" ;;
    xyz) echo "\$variable = xyz" ;;
esac</pre>
</td>
</tr>
<tr>
<td>;;&amp;、;&amp;</td>
<td>Bash4+的CASE子句终止符</td>
</tr>
<tr>
<td>.</td>
<td>点号命令，相当于source命令</td>
</tr>
<tr>
<td>.</td>
<td>作为文件名的组成部分：隐藏文件的前缀</td>
</tr>
<tr>
<td>.</td>
<td>正则式字符串匹配，匹配任何单字符</td>
</tr>
<tr>
<td>"</td>
<td>部分引用，保留字符串中的大部分字符（继续重新解释：$、\、`），用于括起字符串，如果其中有特殊字符</td>
</tr>
<tr>
<td>'</td>
<td>完全引用，保留字符串中的全部字符（无法表示'字符）</td>
</tr>
<tr>
<td>,</td>
<td>
<p>串联多个表达式，最后一个作为返回值：
<pre class="crayon-plain-tag">let "t2 = ((a = 9, 15 / 3))" #设置t2=15/3，a=9</pre>
</td>
</tr>
<tr>
<td>, ,,</td>
<td>参数替换时的小写转换，双字符表示所有字符都转换，单字符表示只转换第一个字符（逗号）</td>
</tr>
<tr>
<td>\</td>
<td>
<p>转义，后面的字符按字面解析（Shell不做特殊解释）。
<p>\n 意味着新的一行<br />\r 回车<br />\t Tab键<br />\v 垂直Tab,查前边的Ctl-K<br />\b backspace,查前边的Ctl-H<br />\a 报警响铃<br />\0xx 转换成8进制ASCII 解码</p>
</td>
</tr>
<tr>
<td>\</td>
<td>
<p>后面跟命令，例如\cp，可以临时禁用alias</p>
</td>
</tr>
<tr>
<td>/</td>
<td>文件路径分隔符</td>
</tr>
<tr>
<td>`</td>
<td>命令替换，将命令的输出赋值为变量，在子shell中执行</td>
</tr>
<tr>
<td>:</td>
<td>
<p>空命令，什么都不做，exit status=0(true)</p>
<pre class="crayon-plain-tag">if condition
then : #什么都不做
else 
    take-some-action
fi
: &gt;&gt; target_file #如果不存在，则创建文件</pre>
</td>
</tr>
<tr>
<td>!</td>
<td>反转测试或者exit status的值</td>
</tr>
<tr>
<td>*</td>
<td>文件名通配符</td>
</tr>
<tr>
<td>*</td>
<td>正则式中，表示任意数量的字符</td>
</tr>
<tr>
<td>*</td>
<td>乘号</td>
</tr>
<tr>
<td>**</td>
<td>
<p>幂
</td>
</tr>
<tr>
<td>**</td>
<td>扩展文件匹配</td>
</tr>
<tr>
<td>?</td>
<td>参数替换中，用于测试变量是否被设置</td>
</tr>
<tr>
<td>?</td>
<td>通配符，匹配单个字符</td>
</tr>
<tr>
<td>$</td>
<td>变量替换：表示变量的内容，例如 var1=5 echo $var1</td>
</tr>
<tr>
<td>$</td>
<td>在正则式中表示行的结尾</td>
</tr>
<tr>
<td>${}</td>
<td>参数替换</td>
</tr>
<tr>
<td>$' ... '</td>
<td>引用字符串扩展</td>
</tr>
<tr>
<td>$*  $@</td>
<td>位置参数</td>
</tr>
<tr>
<td>$?</td>
<td>退出状态码，包含一个命令、函数、脚本的返回值</td>
</tr>
<tr>
<td>$$</td>
<td>持有进程的标识符</td>
</tr>
<tr>
<td>()</td>
<td>命令组。在子shell中执行，父shell无法见到其中的变量</td>
</tr>
<tr>
<td>()</td>
<td>数组初始化：Array=(element1 element2 element3)</td>
</tr>
<tr>
<td>{xx,yy,zz}</td>
<td>
<p>花括号扩展：注意花括号中不能出现空格，除非\转义，或者引号包含之</p>
<pre class="crayon-plain-tag">echo \"{These,words,are,quoted}\"
#打印："These" "words" "are" "quoted"
cat {file1,file2,file3} &gt; combined_file
#将三个文件的内容连接合并到combined_file
cp file22.{txt,backup}
#复制file22.txt为file22.backup</pre>
</td>
</tr>
<tr>
<td>{a..z}</td>
<td>
<p>扩展的花括号扩展：
<pre class="crayon-plain-tag">echo {a..z} 
#打印 a b c d e f g h i j k l m n o p q r s t u v w x y z
echo {0..3} 
#打印 0 1 2 3
base64_charset=( {A..Z} {a..z} {0..9} + / = )
#初始化一个数组</pre>
</td>
</tr>
<tr>
<td>{}</td>
<td>
<p>代码块，亦称内联组（inline group），创建一个匿名函数，但是其内部变量对外部可见。不会发起子Shell
<pre class="crayon-plain-tag">#!/bin/bash
File=/etc/fstab
{
    read line1
    read line2
} &lt; $File
echo "第一行:"
echo "$line1"
echo
echo "第二行:"
echo "$line2"
{
    echo "$line1"
} &gt; $File
exit 0</pre>
</td>
</tr>
<tr>
<td>[]</td>
<td>测试其中的表达式</td>
</tr>
<tr>
<td>[[ ]]</td>
<td>测试其中的表达式，比[]更灵活</td>
</tr>
<tr>
<td>[ ]</td>
<td>数组元素：Array[1]=slot_1 echo ${Array[1]}</td>
</tr>
<tr>
<td>[ ]</td>
<td>在正则式中表示一个范围内的字符</td>
</tr>
<tr>
<td>$[ ... ]</td>
<td>
<p>（废弃）整数扩展，估算其中的整数值：
<pre class="crayon-plain-tag">a=3
b=7
echo $[$a+$b] # 10
echo $[$a*$b] # 21</pre>
</td>
</tr>
<tr>
<td>(( ))</td>
<td>
<p>算术扩展，和let 命令很像，允许算术运算和赋值。特别的，在<span style="background-color: #c0c0c0;">某些场景下，允许使用C风格的语法</span>，举例：
<pre class="crayon-plain-tag">#将a赋值为8
a=(( 5 + 3 ));
#双圆括号也是一种在Bash 中允许使用C 风格的变量处理的机制，注意下面赋值的空格
(( a = 23 ))
(( a++ ))
(( a-- ))
(( t = a&lt;45?7:11 )) # C 风格的3元操作</pre>
</td>
</tr>
<tr>
<td>重定向符</td>
<td>
<p>command <span style="background-color: #c0c0c0;">&lt;</span> filename：命令的<span style="background-color: #c0c0c0;">标准输入</span>来自文件<br />command <span style="background-color: #c0c0c0;">&gt;</span> filename：重定向脚本的<span style="background-color: #c0c0c0;">标准输出</span>到文件，如果有内容，覆盖<br />command <span style="background-color: #c0c0c0;">&amp;&gt;</span> filename：重定向命令的<span style="background-color: #c0c0c0;">stdout、stderr</span>到文件<br />command <span style="background-color: #c0c0c0;">&gt;&amp;2</span>：重定向命令的<span style="background-color: #c0c0c0;">stdout到stderr<br /></span>command <span style="background-color: #c0c0c0;">&gt;</span> file1 <span style="background-color: #c0c0c0;">2&gt;</span>file2：重新<span style="background-color: #c0c0c0;">标准输出到file1，标准错误到file2<br /></span>command <span style="background-color: #c0c0c0;">&gt;</span> file <span style="background-color: #c0c0c0;">2&gt;&amp;1</span>：<span style="background-color: #c0c0c0;">先重定向stderr到stdout，然后统一重定向到file</span><br />scriptname <span style="background-color: #c0c0c0;">&gt;&gt;</span> filename：<span style="background-color: #c0c0c0;">附加脚本的标准输出</span>到文件<br />[i] <span style="background-color: #c0c0c0;">&lt;&gt;</span> filename 打开文件用于<span style="background-color: #c0c0c0;">读写，并分配文件描述符</span>，没文件则创建<br />&lt;&lt; here document中使用的重定向<br />&lt;&lt;&lt; here string中使用的重定向<br /><span style="background-color: #c0c0c0;">&gt;|</span> 强制重定向，<span style="background-color: #c0c0c0;">强制覆盖</span>文件内容
</td>
</tr>
<tr>
<td>进程替换</td>
<td>(command)&gt;<br />&lt;(command)</td>
</tr>
<tr>
<td>&lt;, &gt;</td>
<td>
<p>ASCII比较</p>
<pre class="crayon-plain-tag">veg1=carrots
veg2=tomatoes
if [[ "$veg1" &lt; "$veg2" ]]
then :
else :
fi</pre>
</td>
</tr>
<tr>
<td>\&lt;, \&gt;</td>
<td>正则式中表示单词边界</td>
</tr>
<tr>
<td>|</td>
<td>
<p>管道，把前面命令的stdout作为后一个命令或者shell的stdin
<pre class="crayon-plain-tag">#打印所有.lst文件，并排序和删除重复行
cat *.lst | sort | uniq</pre>
<p> 管道作为子进程运行，因此不能修改变量</p>
</td>
</tr>
<tr>
<td>||</td>
<td>逻辑或，在测试中，导致返回0（成功），只要任何一个表达式为true</td>
</tr>
<tr>
<td>&amp;</td>
<td>在后台运行一个任务</td>
</tr>
<tr>
<td>&amp;&amp;</td>
<td>逻辑与</td>
</tr>
<tr>
<td>-</td>
<td>简写的命令选项的前缀</td>
</tr>
<tr>
<td>--</td>
<td>命令选项前缀</td>
</tr>
<tr>
<td>-</td>
<td>上一个工作目录：cd -，相当于$OLDPWD</td>
</tr>
<tr>
<td>-</td>
<td>减号</td>
</tr>
<tr>
<td>-</td>
<td>很多命令中表示标准输入，例如cat、sed</td>
</tr>
<tr>
<td>=</td>
<td>赋值符</td>
</tr>
<tr>
<td>+</td>
<td>加号</td>
</tr>
<tr>
<td>+</td>
<td>命令选项，部分命令使用+表示启用某些特性，-表示禁用某些特性</td>
</tr>
<tr>
<td>%</td>
<td>模</td>
</tr>
<tr>
<td>~</td>
<td>家目录</td>
</tr>
<tr>
<td>~+</td>
<td>当前工作目录，相当于$PWD</td>
</tr>
<tr>
<td>~-</td>
<td>上一个工作目录：cd -，相当于$OLDPWD</td>
</tr>
<tr>
<td>=~</td>
<td>正则式匹配</td>
</tr>
<tr>
<td>^</td>
<td>正则式中表示行的开始</td>
</tr>
<tr>
<td>^ ^^</td>
<td>参数替换时的大写转换，双字符表示所有字符都转换，单字符表示只转换第一个字符</td>
</tr>
<tr>
<td>控制字符</td>
<td>Ctl-A 光标移到行首<br />Ctl-B 后退符<br />Ctl-C 终止前端任务<br />Ctl-D 从Shell注销<br />Ctl-E 移动光标到行尾<br />Ctl-F 移动光标向前一字符<br />Ctl-G 响铃<br />Ctl-H 擦除字符<br />Ctl-I 水平制表<br />Ctl-J 新一行<br />Ctl-K 垂直制表<br />Ctl-L 清屏<br />Ctl-M 回车<br />Ctl-N 从历史缓冲中删除一行<br />Ctl-O 新一行<br />Ctl-P 撤销历史缓冲中最后一条命令<br />Ctl-Q 恢复终端的stdin<br />Ctl-R 在历史缓冲中反向查找<br />Ctl-S 暂停，冻结终端的stdin<br />Ctl-U 删除光标到行首的所有字符,在某些设置下,删除全行<br />Ctl-V 当输入字符时,Ctl-V 允许插入控制字符。echo -e '\x0a' 等价于 echo <br />Ctl-W 删除当前光标到前边的最近一个空格之间的字符<br />Ctl-Z 终止前台工作</td>
</tr>
<tr>
<td>空白符</td>
<td>作为命令、变量之间的分隔符。在某些情况下，例如赋值，空白符是禁止的</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">变量与参数</span></div>
<div class="blog_h2"><span class="graybg">基础知识</span></div>
<div class="blog_h3"><span class="graybg">变量替换</span></div>
<p>变量的名称是作为其值的占位符，获取其值的操作称为变量替换——使用$前缀执行。在以下情况下，<span style="background-color: #c0c0c0;"><strong><span style="color: #ff0000;">不需要$前缀</span></strong>：变量声明、变量赋值、read、uset、export、((算术表达式))、或者作为代表信号的特殊情况下</span>。在<span style="background-color: #c0c0c0;">双引号中还是会发生变量替换</span>，这被叫做部分引用，或叫弱引用；<span style="background-color: #c0c0c0;">在单引号中就不会发生变量替换</span>，这叫做全引用，也叫强引用。</p>
<p><span style="background-color: #c0c0c0;">$是${}的简写</span>形式。</p>
<div class="blog_h3"><span class="graybg">变量赋值</span></div>
<p>未初始化的变量的值为null。使用这样的变量可能出现问题，但是进行算术运算是允许的，相当于0</p>
<pre class="crayon-plain-tag">a=879
echo "The value of \"a\" is $a."
let a=16+5 #使用let赋值
#在for循环中赋值
for a in 7 8 9 11
do
    echo -n "$a "
done
#在read 命令状态中赋值
echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a."
#把echo 命令的结果传给变量a
a=`ls -l`
echo $a #注意，ls命令输出的空白符，包括换行，均消失
echo "$a" #空白符保留
# 命令替换的另外一种形式
a=$(cat /ect/redhat-release)

# 命令替换嵌套，$()形式嵌套了``形式
pid=$(ps `jcmd | awk '{print $1}' ` | grep passport | awk '{print $1}' | head -1)</pre>
<div class="blog_h3"><span class="graybg"><strong>变量类型</strong></span></div>
<p>Bash不区分变量的类型，在本质上所有变量均是字符串。但是在特定上下文下，又作为数字看待。</p>
<div class="blog_h3"><span class="graybg"><b>特殊变量</b></span></div>
<p><span style="background-color: #c0c0c0;">局部变量</span>：只有在代码块或者函数中可见</p>
<p><span style="background-color: #c0c0c0;">环境变量</span>：Shell启动时，创建自己的环境，并影响所有子Shell</p>
<p><span style="background-color: #c0c0c0;">位置参数</span>：$0, $1, $2, $3...，其中$0代表脚本文件的名字1-9代表参数${10}代表第10个参数，等等。$*、$@表示所有位置参数的数组。使用shift可以左移参数</p>
<div class="blog_h2"><span class="graybg">参数解析</span></div>
<pre class="crayon-plain-tag">#!/bin/bash

# 解析传递给此脚本的参数
# -o 指定短参数，单字符，多个参数连续写在一起，结尾:
# -l 指定长参数，多个参数使用逗号分隔，结尾:
# 此命令修改$@，$N的含义可能发生改变，命令的非选项部分，最好放在前面
getopt -o lc: --long latest,chart:  -- "$@" &gt; /dev/null

LATEST=false
CHART=""

echo '$1'=$1
echo '$2'=$2
echo '$3'=$3
echo '$4'=$4
echo '$5'=$5

while true; do
  case "$1" in
    -l | --latest ) LATEST=true; shift ;;
    -c | --chart )    CHART=$2; shift; shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

echo LATEST=$LATEST
echo CHART=$CHART</pre>
<p>调用上述脚本的示例：</p>
<pre class="crayon-plain-tag">./push.sh  --latest --chart ngress gmem digital
# $1=--latest
# $2=--chart
# $3=ngress
# $4=gmem
# $5=digital
# LATEST=true
# CHART=ngress

./push.sh  --latest  gmem digital
# $1=--latest
# $2=gmem 注意非参数的位置改变
# $3=digital
# $4=
# $5=
# LATEST=true
# CHART=</pre>
<div class="blog_h2"><span class="graybg">变量的间接引用</span></div>
<p>例如：eval var1=<span style="background-color: #c0c0c0;">\$$var2</span></p>
<div class="blog_h2"><span class="graybg">限定类型的变量</span></div>
<p>declare 或者typeset 内建命令(这两个命令是完全一样的)允许指定变量的具体类型</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 命令</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>declare -r var1[=value1]</td>
<td> 与readonly var1 是完全一样，强制指定只读</td>
</tr>
<tr>
<td>declare -i number</td>
<td> 把变量"number"后边的赋值视为一个整型</td>
</tr>
<tr>
<td>declae -a indices</td>
<td> 变量 indices 将被视为数组</td>
</tr>
<tr>
<td>declare -x var3</td>
<td> 把var3 export 出来</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">引号内的字符串</span></div>
<p>引号可以保护字符串的字面值，避免特殊字符被重新解释，或者被Shell扩展</p>
<pre class="crayon-plain-tag">grep '[Ff]irst' *.txt #基于正则式搜索*.txt文件的内容</pre>
<div class="blog_h1"><span class="graybg">参数替换</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 300px; text-align: center;"> 语法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h2">${parameter}</td>
<td> 与$parameter 相同，可以避免混淆</td>
</tr>
<tr>
<td class="blog_h2">${parameter-default}</td>
<td rowspan="2"> 如果 parameter 没被set，那么就使用default。例如：echo ${username-`whoami`}</td>
</tr>
<tr>
<td class="blog_h2">${parameter:-default}</td>
</tr>
<tr>
<td class="blog_h2">${parameter=default}</td>
<td rowspan="2"> 如果 parameter 未设置，那么就设置为default</td>
</tr>
<tr>
<td class="blog_h2">${parameter:=default}</td>
</tr>
<tr>
<td class="blog_h2">
<p>${parameter+alt_value}</p>
</td>
<td rowspan="2"> 如果 parameter 被set 了，那就使用alt_value，否则就使用null 字符串</td>
</tr>
<tr>
<td class="blog_h2">${parameter:+alt_value}</td>
</tr>
<tr>
<td class="blog_h2">${parameter?err_msg}</td>
<td rowspan="2"> 如果 parameter 被set，那就是用set 的值，否则打印err_msg</td>
</tr>
<tr>
<td class="blog_h2">${parameter:?err_msg}</td>
</tr>
<tr>
<td class="blog_h2">${#var}</td>
<td> 字符串长度 </td>
</tr>
<tr>
<td class="blog_h2">${#array}</td>
<td>数组中第一个元素的长度</td>
</tr>
<tr>
<td class="blog_h2">${#array[*]}</td>
<td rowspan="2"> 数组元素的个数</td>
</tr>
<tr>
<td class="blog_h2">${$#array[@]}</td>
</tr>
<tr>
<td class="blog_h2">${array[@]}</td>
<td>将数组扩展为列表</td>
</tr>
<tr>
<td>${var#Pattern}</td>
<td>从$var 开头删除最近匹配$Pattern 的子串</td>
</tr>
<tr>
<td class="blog_h2">${var##Pattern}</td>
<td>从$var 开头删除最远匹配$Pattern 的子串</td>
</tr>
<tr>
<td class="blog_h2">${var%Pattern}</td>
<td> 从$var 结尾删除最近匹配$Pattern 的子串</td>
</tr>
<tr>
<td class="blog_h2">${var%%Pattern}</td>
<td>从$var 结尾删除最远匹配$Pattern 的子串</td>
</tr>
<tr>
<td class="blog_h2">${var:pos}</td>
<td> 变量 var 从位置pos 开始扩展</td>
</tr>
<tr>
<td class="blog_h2">${var:pos:len}</td>
<td> 从位置 pos 开始，并扩展len 长度个字符</td>
</tr>
<tr>
<td class="blog_h2">${var/Pattern/Repl}</td>
<td> 使用 Replacement 来替换var 中的第一个Pattern 的匹配</td>
</tr>
<tr>
<td class="blog_h2">${var//Pattern/Repl}</td>
<td> 全局替换。在var 中所有的匹配，都会用Replacement 来替换</td>
</tr>
<tr>
<td class="blog_h2">${var/#Pattern/Repl}</td>
<td> 如果 var 的前缀匹配到了Pattern，那么就用Replacement 来替换Pattern</td>
</tr>
<tr>
<td class="blog_h2">${var/%Pattern/Repl}</td>
<td> 如果 var 的后缀匹配到了Pattern，那么就用Replacement 来替换Pattern</td>
</tr>
<tr>
<td class="blog_h2">${!varprefix*}</td>
<td rowspan="2"> 使用变量的前缀来匹配前边所有声明过的变量</td>
</tr>
<tr>
<td class="blog_h2">${!varprefix@}</td>
</tr>
<tr>
<td class="blog_h2">${parameter^^}</td>
<td rowspan="2">转换为大写</td>
</tr>
<tr>
<td class="blog_h2">${parameter^}</td>
</tr>
<tr>
<td class="blog_h2">${parameter,,}</td>
<td rowspan="2">转换为小写</td>
</tr>
<tr>
<td class="blog_h2">${parameter,}</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">命令替换</span></div>
<p>处理一个或者多个命令的输出，并字面的（literally）将其赋予另外一个上下文（设置变量、作为另外一个命令的参数、甚至生成for循环的列表）。命令替换的典型的语法是反引用包围的命令。命令替换以subshell形式调用</p>
<pre class="crayon-plain-tag">script_name=`basename $0`
echo "The name of this script is $script_name."

textfile_listing=`ls *.txt`
#命令替换的可选语法：textfile_listing=$(ls *.txt)
echo $textfile_listing

#把文件内容赋值给变量
variable1=`&lt;file1`
variable2=`cat file2`
#命令替换可能导致单词分隔
COMMAND `echo a b` #COMMAND有两个参数a、b
COMMAND "`echo a b`" #一个参数 "a b"
COMMAND `echo` #没有参数
COMMAND "`echo`" #一个空参数</pre>
<div class="blog_h2"><span class="graybg">捕获输出</span></div>
<div class="blog_h3"><span class="graybg">标准输出</span></div>
<pre class="crayon-plain-tag">VAR=$(command)</pre>
<div class="blog_h3"><span class="graybg">标准错误</span></div>
<pre class="crayon-plain-tag">ERROR=$(cat file1.txt nofile.txt 2&gt;&amp;1 &gt; /dev/null)</pre>
<div class="blog_h3"><span class="graybg">同时捕获 </span></div>
<pre class="crayon-plain-tag">VAR=$(cat file1.txt nofile.txt 2&gt;&amp;1) </pre>
<div class="blog_h1"><span class="graybg">算术扩展</span></div>
<p>算数扩展提供了强大的整数算术操作机制。</p>
<pre class="crayon-plain-tag">z=`expr $z + 3`
z=$(($z+3))       #双括号，注意不要和命令替换混淆
z=$((z+3))        #双括号中，变量解引用可有可无
let z=z+3
let "z += 3"      #引号允许在变量赋值语句中使用空格 </pre>
<div class="blog_h1"><span class="graybg">字符串处理</span></div>
<div class="blog_h2"><span class="graybg">字符串长度</span></div>
<pre class="crayon-plain-tag">#下面3种方式均可得到字符串长度
stringZ=abcABC123ABCabc
echo ${#stringZ} # 15
echo `expr length $stringZ` # 15
echo `expr "$stringZ" : '.*'` # 15</pre>
<div class="blog_h2"><span class="graybg">子串匹配</span></div>
<pre class="crayon-plain-tag">expr match "$string" '$substring'    #$substring 是一个正则表达式
expr "$string" : '$substring'        #$substring 是一个正则表达式
#举例
echo `expr match "$stringZ" 'abc[A-Z]*.2'`</pre>
<div class="blog_h2"><span class="graybg">前缀/后缀匹配</span></div>
<pre class="crayon-plain-tag">#                注意：不要加引号
                 # 前缀匹配              # 后缀匹配
if [[ "$line" == flannel* || "$line" == *$K8S_VERSION ]]; then
  echo $line
fi </pre>
<div class="blog_h2"><span class="graybg">子串索引</span></div>
<pre class="crayon-plain-tag">expr index $string $substring    #匹配到子串的第一个字符的位置
#举例
stringZ=abcABC123ABCabc
echo `expr index "$stringZ" C12`   #6</pre>
<div class="blog_h2"><span class="graybg">提取子串</span></div>
<pre class="crayon-plain-tag">#在string中从位置$position 开始提取子串
#如果$string 为"*"或"@",那么将提取从位置$position 开始的位置参数
${string:position} 

#在string中从位置$position 开始提取$length 长度的子串
${string:position:length}

#举例
stringZ=abcABC123ABCabc
echo ${stringZ:0}     # abcABC123ABCabc
echo ${stringZ:1}     # bcABC123ABCabc
echo ${stringZ:7}     # 23ABCabc
echo ${stringZ:7:3}   # 23A
echo ${stringZ:(-4)}  # Cabc
echo ${*:2}           # Echo 出第2个和后边所有的位置参数
echo ${@:2}           # 与前边相同
echo ${*:2:3}         # 从第2个开始,Echo后边3个位置参数
if [ "${1:0:1}" = '-' ]; then echo " $1 is a option"; fi

#在string中从位置$position开始提取$length 长度的子串
expr substr $string $position $length 
#举例
stringZ=abcABC123ABCabc
echo `expr substr $stringZ 1 2` # ab
echo `expr substr $stringZ 4 3` # ABC

#从$string 的开始位置提取$substring,$substring 是一个正则表达式
expr match "$string" '\($substring\)'
expr "$string" : '\($substring\)'
#举例
stringZ=abcABC123ABCabc
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1</pre>
<div class="blog_h2"><span class="graybg">删除子串</span></div>
<pre class="crayon-plain-tag">#从$string 的左边截掉第一个匹配的$substring
${string#substring}
#从$string 的左边截掉最后一个匹配的$substring
${string##substring}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ#a*C} # 123ABCabc   截掉'a'和'C'之间最近的匹配
echo ${stringZ##a*C} # abc        截掉'a'和'C'之间最远的匹配

#从$string 的右边截掉第一个匹配的$substring
${string%substring}
#从$string 的右边截掉最后一个匹配的$substring
${string%%substring}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ%b*c}  # abcABC123ABCa 从$stringZ 的后边开始截掉'b'和'c'之间的最近的匹配</pre>
<div class="blog_h2"><span class="graybg">替换子串</span></div>
<pre class="crayon-plain-tag">#使用$replacement 来替换第一个匹配的$substring
${string/substring/replacement}
#使用$replacement 来替换所有匹配的$substring
${string//substring/replacement}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ/abc/xyz}  # xyzABC123ABCabc
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz

#如果$substring 匹配$string 的开头部分,那么就用$replacement 来替换$substring
${string/#substring/replacement}
#如果$substring 匹配$string 的结尾部分,那么就用$replacement 来替换$substring
${string/%substring/replacement}
#举例
stringZ=abcABC123ABCabc
echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ</pre>
<div class="blog_h1"><span class="graybg">退出与退出状态</span></div>
<p>exit 命令被用来结束脚本，返回一个值来传给父进程，如果没有exit，则最后执行的命令的exit状态被返回。</p>
<p>每个命令都会返回一个 exit 状态(有时候也叫return 状态)，成功返回0，如果返回一个非0 值，通常情况下都会被认为是一个错误码。</p>
<p>变量：<span style="background-color: #c0c0c0;">$?</span>用于读取退出码。</p>
<div class="blog_h1"><span class="graybg">内部变量</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 120px; text-align: center;"> 变量/函数名</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h2"> $BASH</td>
<td>Bash 的二进制执行文件的位置</td>
</tr>
<tr>
<td class="blog_h2"> $BASH_ENV</td>
<td>指向一个 Bash 启动文件，这个启动文件将在调用一个脚本时被读取</td>
</tr>
<tr>
<td class="blog_h2"> $BASH_SUBSHELL</td>
<td>提示subshell 的层次</td>
</tr>
<tr>
<td class="blog_h2"> $BASH_VERSINFO[n]</td>
<td>记录 Bash 安装信息的一个6 元素的数组</td>
</tr>
<tr>
<td class="blog_h2"> $BASH_VERSION</td>
<td>安装在系统上的 Bash 的版本号</td>
</tr>
<tr>
<td class="blog_h2"> $DIRSTACK</td>
<td>在目录栈中最上边的值（将受到pushd 和popd 的影响）</td>
</tr>
<tr>
<td class="blog_h2"> $EDITOR</td>
<td>脚本调用的默认编辑器</td>
</tr>
<tr>
<td class="blog_h2"> $EUID</td>
<td>"effective"用户ID</td>
</tr>
<tr>
<td class="blog_h2"> $FUNCNAME</td>
<td>当前函数的名字</td>
</tr>
<tr>
<td class="blog_h2"> $GLOBIGNORE</td>
<td>文件名的模式匹配列表</td>
</tr>
<tr>
<td class="blog_h2"> $GROUPS</td>
<td>当前用户属于的组</td>
</tr>
<tr>
<td class="blog_h2"> $HOME</td>
<td>用户的 home 目录</td>
</tr>
<tr>
<td class="blog_h2"> $HOSTNAME</td>
<td>主机名</td>
</tr>
<tr>
<td class="blog_h2"> $HOSTTYPE</td>
<td>主机类型，识别系统的硬件，例如i686</td>
</tr>
<tr>
<td class="blog_h2"> $IFS</td>
<td>内部域分隔符，这个变量用来决定 Bash 在解释字符串时如何识别域、单词边界。默认为空白（空格、TAB、换行）</td>
</tr>
<tr>
<td class="blog_h2"> $IGNOREEOF</td>
<td>忽略 EOF</td>
</tr>
<tr>
<td class="blog_h2"> $LINENO</td>
<td>记录它所在的 shell 脚本中它所在行的行</td>
</tr>
<tr>
<td class="blog_h2"> $MACHTYPE</td>
<td>提示系统硬件</td>
</tr>
<tr>
<td class="blog_h2"> $OLDPWD</td>
<td>老的工作目录</td>
</tr>
<tr>
<td class="blog_h2"> $OSTYPE</td>
<td>操作系统类型</td>
</tr>
<tr>
<td class="blog_h2"> $PATH</td>
<td>指向 Bash 外部命令所在的位置，一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等</td>
</tr>
<tr>
<td class="blog_h2"> $PIPESTATUS</td>
<td>数组变量将保存最后一个运行的前台管道的退出码</td>
</tr>
<tr>
<td class="blog_h2"> $PPID</td>
<td>父进程的进程</td>
</tr>
<tr>
<td class="blog_h2"> $PROMPT_COMMAND</td>
<td>保存一个在主提示符($PS1)显示之前需要执行的命令</td>
</tr>
<tr>
<td class="blog_h2"> $PS1</td>
<td>主提示符，具体见命令行上的显示</td>
</tr>
<tr>
<td class="blog_h2"> $PS2</td>
<td>第 2 提示符，当需要额外的输入的时候将会显示，默认为"&gt;"</td>
</tr>
<tr>
<td class="blog_h2"> $PS3</td>
<td>第 3 提示符，在select 循环中显示</td>
</tr>
<tr>
<td class="blog_h2"> $PS4</td>
<td>第 4 提示符，当使用-x 选项调用脚本时，这个提示符将出现在每行的输出前边，默认为"+"</td>
</tr>
<tr>
<td class="blog_h2"> $PWD</td>
<td>工作目录</td>
</tr>
<tr>
<td class="blog_h2"> $REPLY</td>
<td>read命令如果没有给变量，输入将保存在$REPLY 中</td>
</tr>
<tr>
<td class="blog_h2"> $SECONDS</td>
<td>脚本已经运行的时间(单位为秒)</td>
</tr>
<tr>
<td class="blog_h2"> $SHELLOPTS</td>
<td>保存 shell 允许的选项，这个变量是只读的</td>
</tr>
<tr>
<td class="blog_h2"> $SHLVL</td>
<td>Shell层次，就是shell 层叠的层次，如果是命令行那$SHLVL 就是1，递增</td>
</tr>
<tr>
<td class="blog_h2"> $TMOUT</td>
<td>如果$TMOUT 环境变量被设置为一个非零的时间值，那么在过了这个指定的时间之后，shell提示符将会超时，这会引起一个logout</td>
</tr>
<tr>
<td class="blog_h2"> $UID</td>
<td>用户 ID 号</td>
</tr>
<tr>
<td class="blog_h2"> $0, $1, $2...</td>
<td>
<p>位置参数，从命令行传递给脚本，或者是传递给函数</p>
<p>$0 为脚本文件的名字<br />$1 为第一个参数，类推</p>
</td>
</tr>
<tr>
<td class="blog_h2"> $#</td>
<td>命令行或者是位置参数的个数</td>
</tr>
<tr>
<td class="blog_h2"> $*</td>
<td>
<p>所有的位置参数，被作为一个单词。注意：<span style="background-color: #c0c0c0;">"$*"必须被""引用</span></p>
</td>
</tr>
<tr>
<td class="blog_h2"> $@</td>
<td>与$*同义，但是每个参数都是一个独立的""引用字串。注意：<span style="background-color: #c0c0c0;">"$@"必须被""引用</span></td>
</tr>
<tr>
<td class="blog_h2"> $!</td>
<td>在后台运行的最后的工作的 PID</td>
</tr>
<tr>
<td class="blog_h2"> $_</td>
<td>保存之前执行的命令的最后一个参数</td>
</tr>
<tr>
<td class="blog_h2"> $?</td>
<td>命令，函数或者脚本本身的退出状态</td>
</tr>
<tr>
<td class="blog_h2"> $$</td>
<td>脚本自身的进程 ID</td>
</tr>
<tr>
<td class="blog_h2"> $RANDOM</td>
<td> 产生一个随机整数</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">条件测试与分支</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>一个<pre class="crayon-plain-tag">if/then</pre>结构可以测试命令的返回值是否为零（0表示成），如果是的话，执行更多命令。</p>
<p>内建命令<pre class="crayon-plain-tag">[</pre>与<pre class="crayon-plain-tag">test</pre>命令等价。这个命令把它的参数作为比较表达式或是文件测试，并且根据比较的结果，返回一个退出码。</p>
<p>Bash 2.02以后，关键字<pre class="crayon-plain-tag">[[...]]</pre>用于扩展test命令。 例如<pre class="crayon-plain-tag">[[ $a -lt $b ]]</pre>是一个单独的元素，返回一个退出码。</p>
<p><pre class="crayon-plain-tag">((...))</pre>和<pre class="crayon-plain-tag">let...</pre>结果也能够返回一个退出码，当它们所测试的<span style="background-color: #c0c0c0;">算术表达式的结果为<span style="color: #ff0000;"><strong>非0</strong></span></span> 的时候，退出码将返回0。</p>
<p><pre class="crayon-plain-tag">[[]]</pre>结构比<pre class="crayon-plain-tag">[]</pre>更加灵活，前者支持<span style="background-color: #c0c0c0;">参数扩展和命令替换</span>。</p>
<div class="blog_h2"><span class="graybg">test/[命令</span></div>
<div class="blog_h3"><span class="graybg">语法</span></div>
<pre class="crayon-plain-tag">test EXPRESSION

test

[ EXPRESSION ]

[ ]

[ OPTION</pre>
<div class="blog_h3"><span class="graybg">表达式</span></div>
<p>EXPRESSION的语法如下：</p>
<pre class="crayon-plain-tag"># 空白表达式为假


# 如果表达式为真，通过测试（结果为真）
( EXPRESSION )

# 如果表达式为假，通过测试
! EXPRESSION

# 逻辑与
EXPRESSION1 -a EXPRESSION2

# 逻辑或
EXPRESSION1 -o EXPRESSION2

# 字符串长度不为零，通过测试
-n STRING

# 如果字符串长度为零，通过测试
-z STRING

# 字符串比较、文件测试、数字比较见下文</pre>
<div class="blog_h2"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">[ "$1" = 'mysqld' -a -n "$CLUSTER_INIT" ]   # 如果第一个参数为mysqld，并且变量CLUSTER_INIT不为空

(( 0 &amp;&amp; 1 ))                                # 逻辑与，$?为1
let "num = (( 0 &amp;&amp; 1 ))"                    # num的值为0，故$?为1
(( 200 || 11 ))                             # 逻辑或，$? # 0
let "num = (( 200 || 11 ))"                 # num的值为1，故$?为0
(( 200 | 11 ))                              # 按位或，$?为0     
   
# 注意，本行不会打印var变量，因为算术扩展的退出状态不是一个错误码                                  
var=-2 &amp;&amp; (( var+=2 )) &amp;&amp; echo $var     

# 测试任何命令    
if grep -q Bash file                        
    then echo "At least one Bash in file"
fi
# []或者[[]]测试时，必须与内部表达式之间保留一个空格

# 如果在同一行上边写多个子句，必须使用; 来分隔（注意后面的空格）
if [ ! -x "$filename" ]; then :               #空命令:不执行任何动作
else if [0]; then :
elif [1]; then :                            #elif是else if的简写
fi

# 测试上一个命令的退出码
if [[ $? -eq 0 ]] ; then echo 'Successful'; fi
if [[ $? -eq 0 ]] ; then
    echo 'Succeeded'
    echo 'OK'
else
    echo 'Failed'
fi

# 测试第四个参数是否存在
if [ -z "$4" ] ; then
   echo "Argument 4 not exist"
fi

# 所有内容集中在单行编写的例子
if [[ $? -eq 0 ]]; then echo FOUND; fi
# 使用逻辑与/或
if [[ 1 -eq 1 &amp;&amp; 2 eq 2 ]]; then : fi
if [[ 1 -eq 1 ]] || [[ 2 -eq 1 ]]; then : fi</pre>
<div class="blog_h2"><span class="graybg">文件测试</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;">测试符 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-e</td>
<td>文件存在</td>
</tr>
<tr>
<td>-f</td>
<td>file 是一个普通文件，如果file是目录、设备文件，或者file不存在，则测试不通过</td>
</tr>
<tr>
<td>-s</td>
<td>文件长度不为0</td>
</tr>
<tr>
<td>-d</td>
<td>文件是个目录</td>
</tr>
<tr>
<td>-b</td>
<td>文件是个块设备(软盘,cdrom 等等)</td>
</tr>
<tr>
<td>-c</td>
<td>文件是个字符设备(键盘,modem,声卡等等)</td>
</tr>
<tr>
<td>-p</td>
<td>文件是个管道</td>
</tr>
<tr>
<td>-h</td>
<td>文件是个符号链接</td>
</tr>
<tr>
<td>-L</td>
<td>文件是个符号链接</td>
</tr>
<tr>
<td>-S</td>
<td>文件是个socket</td>
</tr>
<tr>
<td>-t</td>
<td>关联到一个终端设备的文件描述符<br />这个选项一般都用来检测是否在一个给定脚本中的 stdin[-t0]或[-t1]是一个终端</td>
</tr>
<tr>
<td>-r</td>
<td>文件具有读权限(对于用户运行这个test)</td>
</tr>
<tr>
<td>-w</td>
<td>文件具有写权限(对于用户运行这个test)</td>
</tr>
<tr>
<td>-x</td>
<td>文件具有执行权限(对于用户运行这个test)</td>
</tr>
<tr>
<td>-g</td>
<td>目录是否具有 sgid 标志</td>
</tr>
<tr>
<td>-u</td>
<td>文件是否具有suid标志。如果运行一个具有 root 权限的文件,那么运行进程将取得root 权限,即使你是一个普通用户，如果没有 suid 标志的话,那么普通用户(没有root 权限)将无法运行这种程序</td>
</tr>
<tr>
<td>-k</td>
<td>
<p>检查sticky bit，设置在文件上，将保存在交换区；设置在目录上，将限制写权限：如果用户不是该目录的所有者，但是具有写权限，那么他只能删除目录下自己拥有的文件。</p>
<p>设置了此标志位的文件或目录（例如/tmp），权限尾部有t标记，例如：drwxrwxrwt</p>
</td>
</tr>
<tr>
<td>-O</td>
<td>是否当前用户是文件所有者</td>
</tr>
<tr>
<td>-G</td>
<td>是否文件的group-id 与当前用户相同</td>
</tr>
<tr>
<td>-N</td>
<td>从文件最后被阅读到现在，是否被修改</td>
</tr>
<tr>
<td>f1 -nt f2</td>
<td>是否文件 f1 比f2 新</td>
</tr>
<tr>
<td>f1 -ot f2</td>
<td>是否文件 f1 比f2 老</td>
</tr>
<tr>
<td>f1 -ef f2</td>
<td>是否f1和f2 都硬连接到同一个文件</td>
</tr>
<tr>
<td>!</td>
<td>可以反转上述测试的结果</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">其他比较操作符</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> 操作符</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;" colspan="2"><strong> 整数比较</strong></td>
</tr>
<tr>
<td> -eq</td>
<td>等于，如：if [ "$a" -eq "$b" ]</td>
</tr>
<tr>
<td> -ne</td>
<td>不等于，如：if [ "$a" -ne "$b" ]</td>
</tr>
<tr>
<td> -gt</td>
<td>大于，如：if [ "$a" -gt "$b" ]</td>
</tr>
<tr>
<td> -ge</td>
<td>大于等于，如：if [ "$a" -ge "$b" ]</td>
</tr>
<tr>
<td> -lt</td>
<td>小于，如：if [ "$a" -lt "$b" ]</td>
</tr>
<tr>
<td> -le</td>
<td>小于等于，如：if [ "$a" -le "$b" ]</td>
</tr>
<tr>
<td> &lt;</td>
<td>小于(需要双括号)，如：(("$a" &lt; "$b"))</td>
</tr>
<tr>
<td> &lt;=</td>
<td>小于等于(需要双括号)，如：(("$a" &lt;= "$b"))</td>
</tr>
<tr>
<td> &gt;</td>
<td>大于(需要双括号)，如：(("$a" &gt; "$b"))</td>
</tr>
<tr>
<td> &gt;=</td>
<td>大于等于(需要双括号)，如：(("$a" &gt;= "$b"))</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"> <strong> 字符串比较</strong></td>
</tr>
<tr>
<td> =</td>
<td>等于，如：if [ "$a" = "$b" ]</td>
</tr>
<tr>
<td> ==</td>
<td>
<p>等于，如：if [ "$a" == "$b" ]，与=等价，注意其行为在[] [[]]中的不同</p>
<p>[[ $a == z* ]] # 如果$a 以"z"开头(模式匹配)那么将为true<br />[[ $a == "z*" ]] # 如果$a 等于z*(字符匹配),那么结果为true<br />[ $a == z* ] # File globbing（一种关于文件的速记法，比如"*.c"、"~"） 和word splitting 将会发生<br />[ "$a" == "z*" ] # 如果$a 等于z*(字符匹配),那么结果为true</p>
</td>
</tr>
<tr>
<td> !=</td>
<td>不等于，如：if [ "$a" != "$b" ]</td>
</tr>
<tr>
<td> &lt;</td>
<td>
<p>小于，按ASCII 字母顺序比较，例如</p>
<p>if [[ "$a" &lt; "$b" ]]<br />if [ "$a" \&lt; "$b" ]  #在[]中，&lt;需要转义</p>
</td>
</tr>
<tr>
<td> &gt;</td>
<td>大于，按ASCII 字母顺序比较</td>
</tr>
<tr>
<td>-z</td>
<td>测试字符串为"null"，即长度为0</td>
</tr>
<tr>
<td>-n</td>
<td>测试字符串不为"null"</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>混合比较</strong></td>
</tr>
<tr>
<td>-a</td>
<td>逻辑与</td>
</tr>
<tr>
<td>-o</td>
<td>逻辑或</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">嵌套的测试</span></div>
<pre class="crayon-plain-tag">if [ condition1 ]
then
    if [ condition2 ]
    then
    do-something # 这里只有在condition1 和condition2 都可用的时候才行.
    fi
fi</pre>
<div class="blog_h2"><span class="graybg">CASE分支结构</span></div>
<pre class="crayon-plain-tag">case "$variable" in
"$condition1")         #测试行都以右小括号)结尾
    command...
;;                     #条件块都以两个分号结尾
"$condition1")
    command...
;;
esac
#举例
read Keypress
case "$Keypress" in
[[:lower:]] ) echo "Lowercase letter";;
[[:upper:]] ) echo "Uppercase letter";;
[0-9] )       echo "Digit";;
* )           echo "Punctuation, whitespace, or other";;
esac</pre>
<div class="blog_h1"><span class="graybg">循环结构</span></div>
<div class="blog_h2"><span class="graybg">for循环</span></div>
<div class="blog_h3"><span class="graybg">语法</span></div>
<pre class="crayon-plain-tag">#for循环
for arg in [list]
do
    command(s)...
done</pre>
<div class="blog_h3"><span class="graybg">分隔符</span></div>
<p>默认的分隔符是whitespace，也就是每当遇到分隔符，就会产生新的arg。要修改分隔符为换行，在循环前添加：</p>
<pre class="crayon-plain-tag">IFS=$'\n'</pre>
<div class="blog_h2"><span class="graybg">C风格for循环</span></div>
<pre class="crayon-plain-tag">LIMIT=10
for ((a=1, b=1; a &lt;= LIMIT ; a++, b++))
do
    echo -n "$a "
done

# 无限循环
for (( ; ; ))</pre>
<div class="blog_h2"><span class="graybg">while循环</span></div>
<pre class="crayon-plain-tag">while [condition]
do
    command...
done</pre>
<div class="blog_h2"><span class="graybg">C风格while循环 </span></div>
<pre class="crayon-plain-tag">((a = 1))
while (( a &lt;= LIMIT ))
do
    echo -n "$a "
    ((a += 1)) # let "a+=1"
done</pre>
<div class="blog_h2"><span class="graybg">until循环</span></div>
<pre class="crayon-plain-tag">until [condition-is-true]
do
    command...
done </pre>
<div class="blog_h2"><span class="graybg">循环举例</span></div>
<div class="blog_h3"><span class="graybg">无限循环</span></div>
<pre class="crayon-plain-tag"># 无限循环1
while :

# 无限循环2
while true</pre>
<div class="blog_h3"><span class="graybg">数字范围循环</span></div>
<pre class="crayon-plain-tag"># 遍历范围
var0=0
LIMIT=10
while [ "$var0" -lt "$LIMIT" ]
do
    echo -n "$var0 "
    var0=`expr $var0 + 1` 
    # var0=$(($var0+1))
    # var0=$((var0 + 1))
    # let "var0 += 1"
done</pre>
<div class="blog_h3"><span class="graybg">遍历命令输出</span></div>
<p>可以使用管道：</p>
<pre class="crayon-plain-tag">ip route | while read -r route; do
  dest=$(echo $route | awk '{print $1}')
  if [[ "$dest" != *"/"* ]]; then
    continue
  fi
done</pre>
<p>注意，某些命令结尾没有\n，这会导致遍历的时候最后一行被略过，需要这样：</p>
<pre class="crayon-plain-tag">{ yourcmd; echo; } | while read -r line; do echo $line; done

# 遍历文件的每一行，注意，echo确保文件不以换行符结尾的情况下，最后一行不丢失
{ cat images.txt; echo; } | while read -r line; do
  if [[ "$src" == "" ]]; then
    continue
  fi
done</pre>
<p>使用管道时，while运行在subshell中，缺点是无法修改变量，可以使用Here String规避：</p>
<pre class="crayon-plain-tag">declare -A tgt_service_versions
while read -r line; do
  comp=$(echo "$line" | yq r - '.service_name')
  version=$(echo "$line" | yq r - '.tmplgrp_version')
  tgt_service_versions[$comp]=$version
done &lt;&lt;&lt; "$(yq r global.yaml ".vars.services")"</pre>
<div class="blog_h3"><span class="graybg">遍历字符串列表</span></div>
<pre class="crayon-plain-tag"># 遍历字符串列表，注意，如果字符串加引号，作为整体看待
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
    echo $planet # Each planet on a separate line.
done

# 遍历字符串列表，使用变量
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind"
for file in $FILES
do
    if [ ! -e "$file" ]
        break
        continue 1   # 控制循环的行为，数字用于跳出或继续N层循环
    fi
done</pre>
<div class="blog_h3"><span class="graybg">遍历文件列表 </span></div>
<pre class="crayon-plain-tag"># 遍历文件列表。如果列表中包含通配符*?，则会扩展文件名，即file globbing
#   *所有文件
#   [jx]* 所有j或者x开头的文件
for file in *; do ...
for cfg in config/*; do
    if
done</pre>
<div class="blog_h3"><span class="graybg">遍历参数数组 </span></div>
<pre class="crayon-plain-tag"># 遍历参数数组。如果忽略列表，则操作$@
for a
do
    echo -n "$a "
done </pre>
<div class="blog_h1"><span class="graybg">内建命令</span></div>
<p>一个内建命令通常与一个系统命令同名，但是Bash 在内部重新实现了这些命令</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 命令</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h2" style="width: 200px;">echo</td>
<td>
<p>打印(到stdout)一个表达式或变量，可以用来作为一系列命令的管道输入<br />-e 打印转义字符，如果不指定，斜杠转义按字面输出<br />-n 阻止新起一行<br />注意：echo默认会把换行符替换为空格，要改变此行为，需要在被打印变量外面加""</p>
</td>
</tr>
<tr>
<td class="blog_h2">printf</td>
<td>
<p>格式化输出，是echo 命令的增强。与C语言同名函数类似</p>
</td>
</tr>
<tr>
<td>declare / typeset</td>
<td>
<p><pre class="crayon-plain-tag">declare [-aAfFgilnrtux] [-p] [name[=value] ...]</pre></p>
<p>声明变量，可选的，提供属性</p>
<p>如果不指定任何name，则打印所有变量的值</p>
<p>-p 用于显示变量的值和属性，使用该选项，则其它额外的（除了-f/-F）选项被忽略</p>
<p>-a 声明变量为索引数组<br />-A 声明变量为关联数组<br />-i 变量被作为整数看待，变量被赋值时，自动进行算术扩展<br />-l 赋值的时候，自动转换为小写<br />-u 赋值的时候，自动转换为大写 <br />-r 声明为只读变量，无法被后续赋值或unset<br />-x 变量被export为环境变量</p>
<p>所有选项前面的 - 替换为+，则关闭对应选项</p>
</td>
</tr>
<tr>
<td>local</td>
<td>
<p><pre class="crayon-plain-tag">local [option] [name[=value] ...]</pre></p>
<p>在函数内部使用，声明局部变量，option和declare命令相同</p>
</td>
</tr>
<tr>
<td class="blog_h2">read</td>
<td>
<p>从 stdin 中读取一个变量的值<br />-a 取得数组变量<br />-s 不回显输入<br />-d 读取到该选项指定的字符，而不是默认的换行符<br />-n N 只接受N个字符的输入<br />-p 给出提示文字<br />-t  等待用户输入的时间<br />-r 禁止 \ 进行任何字符的转义</p>
<p>使用read 命令时，输入一个\然后回车，会阻止产生一个新行</p>
</td>
</tr>
<tr>
<td class="blog_h2">cd</td>
<td>-P 忽略符号连接<br />cd - 切换工作目录为$OLDPWD</td>
</tr>
<tr>
<td class="blog_h2">pwd</td>
<td>打印当前的工作目录</td>
</tr>
<tr>
<td class="blog_h2">pushd</td>
<td>pushd dir-name 把路径dir-name 压入目录栈，同时修改当前目录到dir-name</td>
</tr>
<tr>
<td class="blog_h2">popd</td>
<td>popd 将目录栈中最上边的目录弹出，同时修改当前目录到弹出来的那个目录</td>
</tr>
<tr>
<td class="blog_h2">dirs</td>
<td>列出所有目录栈的内容</td>
</tr>
<tr>
<td class="blog_h2">let</td>
<td>执行变量的算术操作，可以看作expr的简化版</td>
</tr>
<tr>
<td class="blog_h2">eval</td>
<td><pre class="crayon-plain-tag">eval arg1 ... [argN]</pre>  将表达式中的参数、或者表达式列表组合起来并估算</td>
</tr>
<tr>
<td class="blog_h2">set</td>
<td>
<p>修改内部脚本变量的值。或者以一个命令的结果(set `command`)来重新设置脚本的位置参数，例如：set `uname -a`; <br />echo $_</p>
<p>&nbsp;</p>
<p>也可以<span style="background-color: #c0c0c0;">用来设置Bash的行为选项</span>：<br /><span style="background-color: #c0c0c0;"><strong>-e</strong> </span>如果一个管道、子Shell命令（括号中包围）、列表（花括号包围）中的某个命令以非0退出，则立即退出Shell。以下情况除外：</p>
<ol>
<li>如果命令列表后面是while/until关键字</li>
<li>如果命令位于if/elif测试中</li>
<li>位于&amp;&amp;或||中，且不在尾部</li>
<li>位于管道（Pipeline）中，且不在尾部</li>
</ol>
<p>你可以在Shell退出前捕获ERR信号</p>
<p><span style="background-color: #c0c0c0;"><strong>-f</strong> </span>禁用路径名（Pathname）展开</p>
<p><strong><span style="background-color: #c0c0c0;">-m</span> </strong>监控模式，启用Job控制，后台进程运行在独立的进程组中</p>
<p><strong><span style="background-color: #c0c0c0;">-o pipefail</span></strong> 如果管道中一个命令失败，则整个管道的退出码为非0</p>
<p>-o errexit  如果某个命令或管道失败，立即退出脚本，等同于 -e</p>
<p><span style="background-color: #c0c0c0;"><strong>+e、+o ... 取消之前的 -e、-o的效果</strong></span></p>
</td>
</tr>
<tr>
<td class="blog_h2">unset</td>
<td>来删除一个shell 变量（设置为null）。对位置参数无效</td>
</tr>
<tr>
<td class="blog_h2">export</td>
<td>使得被export 的变量在运行的脚本（或shell）的所有的子进程中都可用。该命令重要的使用就是用在启动文件中设置环境变量</td>
</tr>
<tr>
<td class="blog_h2">readonly</td>
<td>与 declare -r 作用相同</td>
</tr>
<tr>
<td class="blog_h2">getopts</td>
<td>允许传递和连接多个选项到脚本中，并能分配多个参数到脚本中</td>
</tr>
<tr>
<td class="blog_h2">source / .</td>
<td>
<p>在命令行上执行的时候，将会执行一个脚本</p>
<p>在一个文件内一个source file-name将会加载 file-name 文件；source 一个文件将会在脚本中引入代码，并附加到脚<br />本中（类似于#include）</p>
</td>
</tr>
<tr>
<td class="blog_h2">exit</td>
<td>停止一个脚本的运行。不带参数的exit，等价于exit $?，即最后一条命令的退出码</td>
</tr>
<tr>
<td class="blog_h2">exec</td>
<td>
<p>使用一个特定的命令来取代当前进程。一般的当shell遇到一个命令时，会fork off子进程来运行，使用exec 内建命令将会替换掉当前 shell，命令执行完毕即导致shell进程立即退出</p>
<p>也可以用来重定向脚本的标准输出</p>
</td>
</tr>
<tr>
<td class="blog_h2">shopt</td>
<td>允许 shell 在空闲时修改shell 选项</td>
</tr>
<tr>
<td class="blog_h2">caller</td>
<td>在stdout 上打印出函数调用者的信息</td>
</tr>
<tr>
<td class="blog_h2">ture</td>
<td>一个返回成功（0）退出码的命令</td>
</tr>
<tr>
<td class="blog_h2">flase</td>
<td>一个返回失败（非0）退出码的命令</td>
</tr>
<tr>
<td class="blog_h2">type [command]</td>
<td>将给出command的完整路径</td>
</tr>
<tr>
<td class="blog_h2">bind</td>
<td>令用来显示或修改readline的键绑定</td>
</tr>
<tr>
<td class="blog_h2">shift</td>
<td>
<p>向左移动参数数组$@的元素，可以指定一个数字，表示移动的个数，如果不指定则移动一个</p>
<p>移动1后，$@长度减小1，$1变为￥0</p>
</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">
<p><strong>作业控制命令（Job Control Commands）</strong></p>
<p style="text-align: left;"><strong>作业标识符</strong></p>
<p style="text-align: left;">%N  作业号[N]<br />%S  以字符串S 开头的被(命令行)调用的作业<br />%?S 包含字符串S 的被(命令行)调用的作业<br />%%、%+ 当前作业(前台最后结束的作业,或后台最后启动的作业)<br />%-  当前作业退出后，该作业变成当前作业<br />$!   最后的后台进程</p>
</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">jobs</td>
<td>
<p>在后台列出所有正在运行的作业，给出作业号。<span style="background-color: #c0c0c0;">kill %作业号</span>和<span style="background-color: #c0c0c0;">kill 进程号</span>等价：</p>
<pre class="crayon-plain-tag"># 方括号内的是作业号
# Running表示正在运行的作业， 它会打印到标准输出
[1]- Running ping www.baidu.com &amp;
# +表示当前作业，如果fg、bg不指定参数，则针对当前作业
# Stopped表示挂起的作业，但是进程并没有退出
[2]+  Stopped                 ping www.163.com
# -表示如果当前作业退出，则它会变成当前作业
[3]-  Stopped                 ping www.qq.com</pre>
</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">disown</td>
<td>从 shell 的当前作业表中，删除作业</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">fg</td>
<td>
<p>把一个后台作业放到前台来运行。不指定作业号，则对当前作业进行处理
</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">bg</td>
<td>
<p>新启动一个挂起的作业，并且在后台运行它。不指定作业号，则对当前作业进行处理</p>
</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">wait</td>
<td>停止脚本的运行，直到后台运行的所有作业（或者指定作业号或进程号）都结束为止</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">suspend</td>
<td>类似Control-Z，但是它挂起的是当前shell</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">logout</td>
<td>退出一个登陆的 shell，可以指定一个退出码</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">times</td>
<td>给出执行命令所占的时间</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">kill</td>
<td>通过发送一个适当的结束信号，来强制结束一个进程</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">command</td>
<td>
<p>禁用别名和函数的查找，只查找内部命令以及搜索路径中找到的脚本或可执行程序</p>
<p>Bash查找优先级：1.别名 2.关键字 3.函数 4.内置命令 5.脚本或可执行程序($PATH)</p>
</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">builtin</td>
<td>在"builtin"后边的命令将只调用内建命令</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">enable</td>
<td>禁用内建（-n）命令或者恢复（-a）内建命令</td>
</tr>
<tr>
<td class=" blog_h2" style="text-align: left;">autoload</td>
<td>带有"autoload"声明的函数，在它第一次被调用的时候才会被加载</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">外部、系统与管理命令</span></div>
<p>参见：<a href="/linux-command-faq">Linux命令知识集锦</a></p>
<div class="blog_h1"><span class="graybg">高级特性</span></div>
<div class="blog_h2"><span class="graybg">Here Documents</span></div>
<div>所谓Here Documents，是一种特殊用途的代码块，使用某种形式的I/O重定向来为交互式程序或命令提供输入，其形式如下：</div>
<pre class="crayon-plain-tag">command --args &lt;&lt; EOF
...
#注意在这里会发生变量替换，因此需要必要的转义
\$PWD is $PWD
...
...
EOF

# 将Here Document写入到文件
cat &lt;&lt; EOF  &gt; /path/to/file
drill.exec: {
  cluster-id: "$CLUSTER_ID",
  zk.connect: "$ZK_CONNECT"
}
EOF</pre>
<div class="blog_h3"><span class="graybg">禁止变量替换</span></div>
<p>下面两种语法都可以：</p>
<pre class="crayon-plain-tag">cat - &lt;&lt;'EOF'
$KNOWN
EOF
# 输出 $KNOWN

cat - &lt;&lt;\EOF
$KNOWN
EOF</pre>
<div class="blog_h2"><span class="graybg">Here String</span></div>
<p>可以看作是Here Document的stripped-down形式。字符串被展开，并逐个喂给命令的stdin：</p>
<pre class="crayon-plain-tag">COMMAND &lt;&lt;&lt; $WORD</pre>
<p>示例：</p>
<pre class="crayon-plain-tag">if grep -q "txt" &lt;&lt;&lt; "$VAR"
then
   echo "$VAR contains the substring sequence \"txt\""
fi

String="This is a string of words."
# 将字符串中的每个字符写入为数组元素
read -r -a Words &lt;&lt;&lt; "$String" </pre>
<div class="blog_h2"><span class="graybg">I/O重定向</span></div>
<p>Linux中，有三个文件：stdin、stdout、stderr总是处于打开状态的。这些文件或者任何打开的文件，可以被重定向。所谓重定向，就是捕获来自文件、命令、程序、脚本甚至代码块的输出（output），并将其作为其它文件、命令、程序或者脚本的输入。</p>
<pre class="crayon-plain-tag">#重定向输出到文件，如果不存在，则创建，否则覆盖
ls -lR &gt; dir-tree.list
: &gt; filename     #将文件的内容清空，如果不存在则创建

#重定向输出到文件，如果不存在，则创建，否则附加到文件结尾
ls -lR &gt;&gt; dir-tree.list

1&gt;filename       #把标准输出重定向到文件
2&gt;filename       #把标准错误重定向到文件
&amp;&gt;filename       #把标准输出、错误同时重定向到文件

LOGFILE=script.log
echo "命令的输出被重定向到文件描述符" 1&gt;$LOGFILE
echo "命令的错误被重附加到文件描述符" 2&gt;&gt;$LOGFILE

2&gt;&amp;1             #把标准错误重定向到标准输出
i&gt;&amp;j             #重定向文件描述符i到文件描述符j
&gt;&amp;j              #重定向默认文件描述符（1，stdout）到j

0&lt; FILENAME      #从文件读取输入

[j]&lt;&gt;filename    #打开文件进行读写，并分配文件描述符j
exec 3&lt;&gt; File    #打开File，并分配3位文件描述符
read -n 4 &lt;&amp;3    #从文件描述符3读入4字符
echo -n . &gt;&amp;3    #输出一个小数点到文件描述符3
exec 3&gt;&amp;-        #关闭文件描述符3

n&lt;&amp;-             #关闭输入文件描述符n
0&lt;&amp;-, &lt;&amp;-        #关闭标准输入
n&gt;&amp;-             #关闭输出文件描述符n
1&gt;&amp;-, &gt;&amp;-        #关闭标准输出

while [ "$name" != Smith ]
do
    read name
    echo $name
    let "count += 1"
done &lt;"$Filename"#重定向代码块的输入</pre>
<div class="blog_h2"><span class="graybg">子Shell</span></div>
<p>Subshell指Shell发动的子进程。一般的，<span style="background-color: #c0c0c0;">外部命令会发动子Shell</span>，而内置命令则不会，此外，括号包含的命令列表发动Subshell：</p>
<pre class="crayon-plain-tag">( command1; command2; command3; ... )

#Subshell可用于构建专用运行环境
COMMAND1
COMMAND2
COMMAND3
(
    IFS=:
    PATH=/bin
    unset TERMINFO
    set -C
    shift 5
    COMMAND4
    COMMAND5
    exit 3 #这里只会退出Subshell
)</pre>
<p><span style="background-color: #c0c0c0;"> Subshell中的变量，在代码块以外无法访问</span>，相当于局部变量。</p>
<div class="blog_h2"><span class="graybg">受限Shell</span></div>
<p>在受限模式下运行的Shell，一些命令被禁用：</p>
<ol>
<li>使用cd改变当前目录</li>
<li>修改环境变量$PATH, $SHELL, $BASH_ENV,  $ENV</li>
<li>读取或者改变$SHELLOPTS</li>
<li>重定向输出</li>
<li>调用包含/符号的命令</li>
<li>调用exec命令来替换Shell的进程</li>
<li>解除受限模式</li>
<li>其它一些命令</li>
</ol>
<p>使用set -r、set --restricted开启脚本受限模式。</p>
<div class="blog_h2"><span class="graybg">进程替换</span></div>
<p>进程替换把一些进程的输出作为其它进程的输入来使用。命令格式如下（使用括号包围），注意没有空格：</p>
<p>&gt;(command_list)<br />&lt;(command_list)</p>
<div class="blog_h2"><span class="graybg">函数</span></div>
<p>函数语法：</p>
<pre class="crayon-plain-tag">function function_name {
    command...
}
#第二种语法支持C-Style
function_name () {
    if [ -z "$1" ] #$1表示第一个参数
    then
        echo "-Parameter #1 is zero length.-"
    else
        echo "-Param #1 is \"$1\".-"
    fi
}
#调用函数
function_name 
function_name $arg1 $arg2  #传参，只支持传值方式，可以使用间接引用来传引用
#支持函数嵌套
f1 ()
{
    f2 () # nested
    {
        echo "Function \"f2\", inside \"f1\"."
    }
}</pre>
<p> 函数的退出：函数返回一个值，称为exit status，这与命令退出码类似。可以使用return语句指定返回值，否则，最后一条命令的退出码作为返回值。</p>
<div class="blog_h2"><span class="graybg">别名</span></div>
<p>Bash别名可以简单的看作一个键盘快捷方式：</p>
<pre class="crayon-plain-tag">alias ll="ls -l"</pre>
<div class="blog_h2"><span class="graybg">列表构造</span></div>
<p>and list：如果前一个命令的退出码为0，则继续执行后面的命令</p>
<pre class="crayon-plain-tag">command-1 &amp;&amp; command-2 &amp;&amp; command-3 &amp;&amp; ... command-n</pre>
<p> or list：如果前一个命令退出码味非0，则继续执行后面的命令</p>
<pre class="crayon-plain-tag">command-1 || command-2 || command-3 || ... command-n</pre>
<div class="blog_h2"><span class="graybg">数组</span></div>
<p>较新版本的Bash支持一维数组，数组元素以<pre class="crayon-plain-tag">variable[xx]</pre>的方式进行访问，通过<pre class="crayon-plain-tag">${element[xx]}</pre>来获取元素的值。</p>
<pre class="crayon-plain-tag">area[11]=23
area[13]=37
area[51]=UFOs

echo ${area[11]}
area[5]=`expr ${area[11]} + ${area[13]}`

area2=( zero one two three four )
area3=([17]=seventeen [24]=twenty-four)
base64_charset=( {A..Z} {a..z} {0..9} + / = )</pre>
<div class="blog_h3"><span class="graybg">数组长度</span></div>
<p>没有下标的索引点，不会占用元素个数：</p>
<pre class="crayon-plain-tag">adobe=([0]='Flash' [2]='Flex' [4]='Photoshop')
echo ${#adobe[@]}
# 打印 3 </pre>
<div class="blog_h3"><span class="graybg">扩展为列表</span></div>
<p>可以使用表达式：<pre class="crayon-plain-tag">${array[@]}</pre>将数组转换为列表：</p>
<pre class="crayon-plain-tag"># 声明一个数组，它的元素来自DOCKER数组的所有成员 +
#                                    run字面之 +
#                                         docker_run_opts数组的所有成员 +
#                                                                KUBE_BUILD_IMAGE 变量
local -ra docker_cmd=("${DOCKER[@]}" run "${docker_run_opts[@]}" "${KUBE_BUILD_IMAGE}")</pre>
<div class="blog_h3"><span class="graybg">连接操作</span></div>
<p>通过扩展操作，可以连接两个数组：</p>
<pre class="crayon-plain-tag">adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere')
adobe2=('Fireworks' 'Illustrator')
adobe3=(${adobe[@]} ${adobe2[@]}) </pre>
<div class="blog_h3"><span class="graybg">切片操作</span></div>
<pre class="crayon-plain-tag">adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere')
echo ${adobe[@]:1:3}
# 打印 Flex Photoshop Dreamweaver
echo ${adobe[@]:3}
# 打印 Dreamweaver Premiere</pre>
<div class="blog_h3"><span class="graybg">文件读取为数组</span></div>
<pre class="crayon-plain-tag">names=(`cat 'names.txt'`)</pre>
<div class="blog_h3"><span class="graybg">遍历数组</span></div>
<pre class="crayon-plain-tag">adobe=('Flash' 'Flex' 'Photoshop' 'Dreamweaver' 'Premiere')

for item in ${adobe[@]};do
	echo $item
done

len=${#adobe[@]}
for ((i=0;i&lt;$len;i++));do
	echo ${adobe[$i]}
done </pre>
<div class="blog_h2"><span class="graybg">关联数组</span></div>
<p>关联数组也就是字典/映射，声明语法：</p>
<pre class="crayon-plain-tag">declare -A archMap=(
  [x86_64]=amd64
  [aarch64]=arm64
)</pre>
<div class="blog_h3"><span class="graybg">数组长度</span></div>
<pre class="crayon-plain-tag">${#filetypes[*]}
${#filetypes[@]}</pre>
<div class="blog_h3"><span class="graybg">写入元素</span></div>
<pre class="crayon-plain-tag">archMap[arm64]=aarch64 </pre>
<div class="blog_h3"><span class="graybg">获取元素</span></div>
<pre class="crayon-plain-tag">arch=x86_64
${archMap[$arch]}</pre>
<div class="blog_h3"><span class="graybg">获取键列表</span></div>
<pre class="crayon-plain-tag"># 使用*，并且整个表达式用双引号包围，则结果是一个字符串
${!filetypes[*]}
# 使用@，并且整个表达式用双引号包围，则结果是一个数组
${!filetypes[@]}</pre>
<div class="blog_h3"><span class="graybg">获取值列表 </span></div>
<pre class="crayon-plain-tag"># 使用*，并且整个表达式用双引号包围，则结果是一个字符串
"${filetypes[*]}"
# 使用@，并且整个表达式用双引号包围，则结果是一个数组
${filetypes[@]}</pre>
<div class="blog_h3"><span class="graybg">遍历数组</span></div>
<pre class="crayon-plain-tag">for key in "${!filetypes[@]}"; do 
  echo ${filetypes[$key]}
done</pre>
<div class="blog_h2"><span class="graybg">/dev和/proc</span></div>
<p>Unix、Linux的文件系统中，通常都有这两个特殊用途的子目录。</p>
<div class="blog_h3"><span class="graybg"><strong>/dev</strong></span></div>
<p>包含设备文件（device files），包括环回文件（loopback devices，例如/dev/loop0。所谓环回文件是一种虚拟机制，可以将一个普通文件作为块设备访问）。此目录下还包含若干常用的伪设备：/dev/null, /dev/zero,/dev/urandom, /dev/sda1, /dev/udp,/dev/tcp。</p>
<p>/dev/null：相当于一个黑洞，可以用于忽略输出：</p>
<pre class="crayon-plain-tag">#忽略命令的输出
cat $filename &gt;/dev/null
#忽略命令的错误
rm $badname 2&gt;/dev/null

#从中无法读取到任何东西，可以用于清空文件内容
cat /dev/null &gt; /var/log/messages</pre>
<p> /dev/zero：该设备制造二进制0，主要用于生成交换文件</p>
<p>当在/dev/tcp/$host/$port这样的伪设备上执行命令时，Bash会打开一个TCP连接：</p>
<pre class="crayon-plain-tag">#从time.nist.gov获取时间
cat /dev/tcp/www.net.cn/80
echo -e "GET / HTTP/1.0\n" &gt;&amp;5
cat &lt;&amp;5</pre>
<div class="blog_h3"><span class="graybg"><strong>/proc</strong></span></div>
<p>该目录中的文件是当前系统中正在运行的系统和内核进程的镜像，包含了运行时和统计信息。可以获取CPU、内存、电池等多种组件的信息。</p>
<div class="blog_h1"><span class="graybg"><a id="trap"></a>信号处理</span></div>
<p>关于Linux信号相关的知识，参考<a href="/linux-process-and-signals">Linux信号、进程和会话</a></p>
<p>注意：<span style="background-color: #c0c0c0;">如果存在正在运行的子进程，Bash默认会忽略发送给父进程的信号</span>。</p>
<div class="blog_h2"><span class="graybg">trap命令</span></div>
<p>当你在终端窗口按下Ctrl + C或者Break键后，正常情况下应用程序会立即停止。但是你可以在脚本中指定信号处理函数。其格式为：</p>
<pre class="crayon-plain-tag"># arg默认为 - 表示捕获到信号时，需要执行的命令
trap [-lp] [[arg] sigspec ...]</pre>
<p>示例用法：</p>
<pre class="crayon-plain-tag"># 列出信号名称和编号的对应关系
trap -l

# 接收到HUP INT PIPE QUIT TERM这几种信号时，执行exit 1命令
trap "exit 1" HUP INT PIPE QUIT TERM

# 也可以指定函数来处理信号
sighdl() {
}
trap sighdl SIGTERM

# 重置信号的默认处理逻辑
trap 1 2

# 忽略信号
trap '' 1</pre>
<div class="blog_h2"><span class="graybg">trap行为</span></div>
<p>当进程收到信号后，在<span style="background-color: #c0c0c0;">同一线程</span>中发生以下事件序列：</p>
<ol>
<li>当前进程正在进行的内置命令（不产生子进程）立即退出</li>
<li>程序转到trap指定的信号处理脚本执行</li>
<li>执行完毕后，到被中断的命令的下一行执行</li>
</ol>
<p>例如下面的脚本：</p>
<pre class="crayon-plain-tag">trap "echo TRAPED" HUP INT PIPE QUIT TERM
while true; do :; done</pre>
<p>当按下Ctrl + C后，信号处理脚本运行，但是然后会继续循环，程序无法停止。下面的代码则可以正常工作：</p>
<pre class="crayon-plain-tag">sighdl() {
    echo TRAPED
    TERM_FLAG=1
}
trap sighdl HUP INT PIPE QUIT TERM

while [ "$TERM_FLAG" != "1" ] ; do :; done </pre>
<div class="blog_h2"><span class="graybg">处理ERR信号</span></div>
<p>设置选项<pre class="crayon-plain-tag">set -o errexit</pre> / <pre class="crayon-plain-tag">set -e</pre>后，任何一个命令出错，都导致脚本退出，并且释放ERR信号。</p>
<p>要捕获这个信号，你需要选项<pre class="crayon-plain-tag">set -o errexit -o errtrace</pre> / <pre class="crayon-plain-tag">set -eE</pre>，并且：</p>
<pre class="crayon-plain-tag"># 出错时清理后台进程
trap 'kill $(jobs -p)' ERR</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">字符串处理</span></div>
<div class="blog_h3"><span class="graybg">分隔字符串</span></div>
<pre class="crayon-plain-tag"># 分割字符串 1,2,3,4,5,6,7,8,9,10
# IFS指定基于什么符号拆分，这里是逗号
IFS=',' read -ra ARRAY &lt;&lt;&lt; `seq -s ',' 10`
for i in "${ARRAY[@]}"; do
    # 遍历分割后的子串
    echo $i 
done</pre>
<div class="blog_h3"><span class="graybg">字符串匹配</span></div>
<p>可以使用Glob：<pre class="crayon-plain-tag">if [[ $line = *"Server startup in"* ]]; then ...</pre></p>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">如何递归遍历目录</span></div>
<pre class="crayon-plain-tag">#!/bin/bash
shopt -s globstar
for file in ./*.go ./**/*.go ; do
	echo $file
done</pre>
<p><pre class="crayon-plain-tag">./*</pre>表示当前目录下的所有文件，<pre class="crayon-plain-tag">./**/*</pre>则表示任意级别子目录下的所有文件。 需要Bash 4.0+。</p>
<div class="blog_h3"><span class="graybg">在脚本内部重定向标准输出</span></div>
<pre class="crayon-plain-tag">exec 2&gt; /var/log/on-startup.log   # 将当前脚本的标准错误重定向到文件
exec 1&gt;&amp;2                         # 将当前脚本的标准输出重定向到标准错误
set -x</pre>
<div class="blog_h3"><span class="graybg">打印到标准错误</span></div>
<pre class="crayon-plain-tag">echo &gt;&amp;2 "ERROR: "</pre>
<div class="blog_h3"><span class="graybg">循环过早退出</span></div>
<p>如果你读取标准输入，并且循环处理：</p>
<pre class="crayon-plain-tag">while read p; do
  cmd $p
done &lt; input.file</pre>
<p><span style="background-color: #c0c0c0;">当循环体中的命令cmd也会读取标准输入时，上述循环会立即退出</span>。</p>
<p>解决办法：</p>
<pre class="crayon-plain-tag">for p in `cat input.file`
do
  cmd $p
done</pre>
<div class="blog_h3"><span class="graybg">sudo echo &gt;报错</span></div>
<p>报错信息：Permission denied</p>
<p>解决办法：<pre class="crayon-plain-tag">sudo sh -c "echo '' &gt; $LOG"</pre></p>
<div class="blog_h3"><span class="graybg">退出命令管道</span></div>
<p>如果在管道的某个命令中需要退出处理，你需要显式的关闭管道中的其它命令（Subshell），简单的例子：</p>
<pre class="crayon-plain-tag">tail -f /usr/local/$TOMCAT_PATH/logs/catalina.out | while read line
do
    if [[ $line = *"Server startup in"*  ]]; then
          # 找到管道前面的命令tail，并杀死
          TAIL_PID=`ps --ppid $PID  | grep tail | awk '{print $1}'`
          echo "Killing subprocess $TAIL_PID"
          kill -9 $TAIL_PID
          exit 0
    fi
done</pre>
<p>如果管道由很多命令组成，可以将它们放置到同一进程组中，然后向进程组发送信号，一起终结它们：</p>
<pre class="crayon-plain-tag"># 项目的命令启用Job控制，导致后台进程运行在独立的进程组
set -m

tail -Fn0 /tmp/report | while :
do 
    echo "pre"
    # 向后台进程组发送信号，使其中的所有进程终结
    sh -c 'PGID=$( ps -o pgid= $$ | tr -d \  ); kill -TERM -$PGID'
    echo "past"
done &amp;  # 在后台运行
wait    # 等待上述后台任务退出 </pre>
<div class="blog_h3"><span class="graybg">得到脚本所在的目录</span></div>
<pre class="crayon-plain-tag">pushd `dirname $0` &gt; /dev/null
SCRIPT_DIR=`pwd`
popd &gt; /dev/null</pre>
<div class="blog_h3"><span class="graybg">转义单引号</span></div>
<p>单引号字符串默认是禁止转义的，如果其中包含成对的单引号本身，会自动被忽略。</p>
<pre class="crayon-plain-tag">echo '''a'''    # a</pre>
<p> 如果其中包含的单引号不成对，属于语法错误。如果要在单引号字符串中使用单引号本身，可以：</p>
<pre class="crayon-plain-tag">echo  $'\'a\''</pre>
<p>即，在字符串前面加上特殊符号$，即可启用ANSI C风格的转义</p>
<div class="blog_h3"><span class="graybg">变量跨行问题</span></div>
<pre class="crayon-plain-tag">#跨行编写的多行字符串变量
mls="
line 1
line 2
line 3
"

#echo的默认行为是把换行符修改为空格输出
echo $mls
#输出：line 1 line 2 line 3

#目标变量外面加上引号，则可避免此默认行为
echo "$mls"
#输出：
#
#line 1
#line 2
#line 3
#
#可以看到，首尾有空白行，这分别是第一个"后的空白行，以及line3后面的空白行

#行尾加上反斜杠，可以把相邻的两行作为单行看待：
mls="\
line 1
line 2
line 3\
"
#再执行echo "$mls"，就不会有空白行了：
#line 1
#line 2
#line 3

mls="\
line 1
line 2\n
line 3\
"
#默认的，反斜杠转义是不被echo命令启用的，除非指定-e
#执行echo -e  "$mls"，会发现\n被转义为换行输出:
#line 1
#line 2

#line 3

#可以使用 $'\n' 把新行添加到变量尾部
mls="$mls"$'\n'"line 4"

#使用Here Document捕获多行文本到变量
read -d '' mls &lt;&lt; EOF
line 1git-study-note
line 2
line 3
EOF
#首尾不会出现空行
echo "$mls"</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/bash-study-note">Bash学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/bash-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
