<?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; 文本处理</title>
	<atom:link href="https://blog.gmem.cc/tag/%e6%96%87%e6%9c%ac%e5%a4%84%e7%90%86/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>Go语言IO编程</title>
		<link>https://blog.gmem.cc/go-io-programming</link>
		<comments>https://blog.gmem.cc/go-io-programming#comments</comments>
		<pubDate>Wed, 01 Feb 2017 09:18:17 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[IO编程]]></category>
		<category><![CDATA[文本处理]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=17909</guid>
		<description><![CDATA[<p>相关包 io 该包为IO操作原语提供了基本的接口，它包装了IO操作原语的实现（例如os包中的类型）。除非特别说明，调用者不能假设接口中的方法可以被并行调用。 常量变量 [crayon-69de22b37315f258977128/] 函数类型 函数 说明 Copy [crayon-69de22b373166466100788-i/] 从src读取数据并写入到dst，直到src的EOF。返回写入dst的字节数，操作成功则err为nil 实现方式：如果src实现了WriterTo接口，则调用src.WriteTo(dst)；如果dst实现了ReaderFrom接口，则调用dst.ReadFrom(src) 示例代码： [crayon-69de22b373169536914163/] CopyBuffer [crayon-69de22b37316b436729355-i/] 和上面类似，只是拷贝时使用给定的缓冲区。如果buf为nil则自动创建一个缓冲区，如果buf长度为0则panic CopyN  [crayon-69de22b37316d479615473-i/] 拷贝N个字节，或知道遇到错误。返回拷贝的字节数和遇到的错误 ReadAtLeast  [crayon-69de22b37316f239932979-i/] <a class="read-more" href="https://blog.gmem.cc/go-io-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/go-io-programming">Go语言IO编程</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">相关包</span></div>
<div class="blog_h2"><span class="graybg">io</span></div>
<p>该包为IO操作原语提供了基本的接口，它包装了IO操作原语的实现（例如os包中的类型）。除非特别说明，调用者不能假设接口中的方法可以被并行调用。</p>
<div class="blog_h3"><span class="graybg">常量变量</span></div>
<pre class="crayon-plain-tag">const (
        SeekStart   = 0 // 相对于文件的起始位置进行Seek
        SeekCurrent = 1 // 相对于当前读取位置进行Seek
        SeekEnd     = 2 // 相对于尾部进行Seek
)

// 错误定义
// 由Read函数返回，表示没有更多可读的数据。用于优雅的结束文件读取（读到尾部了）
var EOF = errors.New("EOF")
// 在关闭的管道上进行读写
var ErrClosedPipe = errors.New("io: read/write on closed pipe")
var ErrNoProgress = errors.New("multiple Read calls return no data or error")、
// 当提供的缓冲区不够存放读取到的数据时
var ErrShortBuffer = errors.New("short buffer")
// 当写操作所要求的字节数不足时
var ErrShortWrite = errors.New("short write")
// 在读取固定长度的块或者数据结构时，没有到预期的结尾位置即发生EOF
var ErrUnexpectedEOF = errors.New("unexpected EOF")</pre>
<div class="blog_h3"><span class="graybg">函数类型</span></div>
<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>Copy</td>
<td>
<p><pre class="crayon-plain-tag">func Copy(dst Writer, src Reader) (written int64, err error)</pre></p>
<p>从src读取数据并写入到dst，直到src的EOF。返回写入dst的字节数，操作成功则err为nil</p>
<p>实现方式：如果src实现了WriterTo接口，则调用src.WriteTo(dst)；如果dst实现了ReaderFrom接口，则调用dst.ReadFrom(src)</p>
<p>示例代码：</p>
<p><pre class="crayon-plain-tag">sr := strings.NewReader("Read after me, please!\n")
n, err := io.Copy(os.Stderr, sr)
if err == nil {
    fmt.Printf("%v bytes read.\n", n)
}</pre>
</td>
</tr>
<tr>
<td>CopyBuffer</td>
<td>
<p><pre class="crayon-plain-tag">func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)</pre></p>
<p>和上面类似，只是拷贝时使用给定的缓冲区。如果buf为nil则自动创建一个缓冲区，如果buf长度为0则panic</p>
</td>
</tr>
<tr>
<td>CopyN </td>
<td>
<p><pre class="crayon-plain-tag">func CopyN(dst Writer, src Reader, n int64) (written int64, err error)</pre></p>
<p>拷贝N个字节，或知道遇到错误。返回拷贝的字节数和遇到的错误</p>
</td>
</tr>
<tr>
<td>ReadAtLeast </td>
<td>
<p><pre class="crayon-plain-tag">func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)</pre></p>
<p>从r读取至少min字节到buf，返回读取的字节数，如果没有读取到至少min字节数，则返回错误：</p>
<ol>
<li>如果一个字节也没有读到，返回EOF</li>
<li>如果读到一部分字节，返回ErrUnexpectedEOF</li>
</ol>
<p>示例：</p>
<p><pre class="crayon-plain-tag">r := bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 7, 8, 9})
buf := make([]byte, 5)
io.ReadAtLeast(r, buf, 3)
fmt.Printf("%v", buf)  // [0 1 2 3 4] 读满了切片容量 </pre>
</td>
</tr>
<tr>
<td>ReadFull</td>
<td>
<p><pre class="crayon-plain-tag">func ReadFull(r Reader, buf []byte) (n int, err error)</pre></p>
<p>读取r的全部内容到buf中，返回实际读取的字节数，如果：</p>
<ol>
<li>如果一个字节也没有读到，返回EOF</li>
<li>如果读到一部分字节（而非r的全部字节），返回ErrUnexpectedEOF</li>
</ol>
</td>
</tr>
<tr>
<td> WriteString</td>
<td>
<p><pre class="crayon-plain-tag">func WriteString(w Writer, s string) (n int, err error)</pre></p>
<p>将字符串s的内容写入到w中，如果w实现了WriteString方法则直接调用之，否则调用其Write一次</p>
</td>
</tr>
<tr>
<td>ByteReader</td>
<td>
<pre class="crayon-plain-tag">type ByteReader interface {
    ReadByte() (byte, error)
}</pre>
</td>
</tr>
<tr>
<td>ByteScanner </td>
<td>
<pre class="crayon-plain-tag">type ByteScanner interface {
    ByteReader
    // 导致下一次ReadByte返回和上一次ReadByte一样的内容
    UnreadByte() error
} </pre>
</td>
</tr>
<tr>
<td>ByteWriter</td>
<td>
<pre class="crayon-plain-tag">type ByteWriter interface {
    WriteByte(c byte) error
} </pre>
</td>
</tr>
<tr>
<td>Closer </td>
<td>
<pre class="crayon-plain-tag">type Closer interface {
    // 此接口包装基本的Close方法
    Close() error
}</pre>
</td>
</tr>
<tr>
<td>LimitedReader </td>
<td>
<pre class="crayon-plain-tag">type LimitedReader struct {
    R Reader // 此结构使用的Reader
    N int64  // 最大读取字节数。每次调用R后等会更新N，来反应还可以读取多少字节
}</pre></p>
<p>该结构上定义的方法：</p>
<p><pre class="crayon-plain-tag">func (l *LimitedReader) Read(p []byte) (n int, err error)</pre></p>
</td>
</tr>
<tr>
<td>Pipe</td>
<td>
<p><pre class="crayon-plain-tag">func Pipe() (*PipeReader, *PipeWriter)</pre></p>
<p>创建一个同步的、内存中的管道，用于连接Reader和Writer</p>
<p>每个PipeWriter.Write调用都会阻塞，直到一个或多个PipeReader.Read了其写入的全部数据，没有内部缓冲区</p>
<p>并行调用Read/Write是安全的</p>
</td>
</tr>
<tr>
<td>PipeReader</td>
<td>
<pre class="crayon-plain-tag">type PipeReader struct {}</pre></p>
<p> 表示管道能读的那一端。该结构上定义以下方法：</p>
<pre class="crayon-plain-tag">// 关闭管道的读端，后续写端的写操作会返回ErrClosedPipe
func (r *PipeReader) Close() error
// 关闭管道的读端，后续写端的写操作会返回该方法提供的err
func (r *PipeReader) CloseWithError(err error) error
// 读取数据
func (r *PipeReader) Read(data []byte) (n int, err error)</pre>
</td>
</tr>
<tr>
<td>PipeWriter</td>
<td>
<pre class="crayon-plain-tag">type PipeWriter struct {}</pre>
<p>表示管道能写的那一端。 该结构上定义以下方法：</p>
<p><pre class="crayon-plain-tag">func (w *PipeWriter) Close() error
func (w *PipeWriter) CloseWithError(err error) error
func (w *PipeWriter) Write(data []byte) (n int, err error) </pre>
</td>
</tr>
<tr>
<td>MultiReader</td>
<td>
<p><pre class="crayon-plain-tag">func MultiReader(readers ...Reader) Reader</pre></p>
<p>串联多个Reader，示例：</p>
<pre class="crayon-plain-tag">r1 := strings.NewReader("Hello")             
r2 := strings.NewReader(" World")            
rm := io.MultiReader(r1, r2)                 
buf := make([]byte, 20)                      
io.ReadFull(rm, buf)                         
fmt.Printf("%v", string(buf)) // Hello World</pre>
</td>
</tr>
<tr>
<td>TeeReader</td>
<td>
<p>&nbsp;
<p><pre class="crayon-plain-tag">func TeeReader(r Reader, w Writer) Reader</pre></p>
<p>返回一个Reader，它会把所有读取到的东西写入到一个Writer中，没有内部缓冲</p>
</td>
</tr>
<tr>
<td>ReaderAt </td>
<td>
<pre class="crayon-plain-tag">type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}</pre></p>
<p>包装ReadAt函数，支持从指定的偏移量开始读取。它从底层数据源偏移量off处开始，读取最多len(p)字节到缓冲区p中</p>
</td>
</tr>
<tr>
<td>ReaderFrom</td>
<td>
<pre class="crayon-plain-tag">type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}</pre></p>
<p> 包装ReadFrom函数，它从r读取数据，直到EOF或者错误</p>
</td>
</tr>
<tr>
<td>NewSectionReader</td>
<td>
<p><pre class="crayon-plain-tag">func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader</pre></p>
<p>返回一个Reader，它从off读取r，读取最多n字节</p>
</td>
</tr>
<tr>
<td>SectionReader</td>
<td>
<pre class="crayon-plain-tag">iotype SectionReader struct {
}
// 读取
func (s *SectionReader) Read(p []byte) (n int, err error)
// 从指定位置开始读取
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
// Seek到指定位置
func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
返回节段中包含的字节数
func (s *SectionReader) Size() int64</pre></p>
<p> 示例：</p>
<pre class="crayon-plain-tag">r := bytes.NewReader([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
s := io.NewSectionReader(r, 2, 3)
buf := make([]byte, 10)
s.Read(buf)
fmt.Printf("%v", buf) // [2 3 4 0 0 0 0 0 0 0] 最多读取3字节

s = io.NewSectionReader(r, 0, 10)
s.ReadAt(buf, 5)
fmt.Printf("%v", buf) // [5 6 7 8 9 0 0 0 0 0] </pre>
</td>
</tr>
<tr>
<td> RuneReader</td>
<td>
<pre class="crayon-plain-tag">type RuneReader interface {
    ReadRune() (r rune, size int, err error)
}</pre>
<p>包装RuneReader函数。ReadRune读取单个UTF-8字符，返回一个rune及其长度 </p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">bufio</span></div>
<p>该包实现带缓冲的IO功能。执行很多碎小的写操作会影响性能，因为每次写操作都对应了系统调用，过多的系统调用会加重CPU负担。类似磁盘这样的设备，处理块对齐的数据时性能更好，因此有必要为IO提供缓冲功能。</p>
<div class="blog_h3"><span class="graybg">缓冲写</span></div>
<p>简单的示例： </p>
<pre class="crayon-plain-tag">// 实现一个仿冒Writer，它能够打印写出的字节数量
type Writer int

func (*Writer) Write(p []byte) (n int, err error) {
    fmt.Printf("Bytes write: %v\n", len(p))
    return len(p), nil
}
func main() {
    w := new(Writer)
    w.Write([]byte{1}) // 一字节无缓冲写出
    w.Write([]byte{2})

    // 创建带缓冲的Writer，缓冲区大小5字节
    bw := bufio.NewWriterSize(w, 5)
    bw.Write([]byte{1, 2, 3}) // 不会写出，因为缓冲区没有满
    bw.Write([]byte{4, 5, 6}) // 写出，输出5
    fmt.Printf("Available size of buffer: %v", bw.Available())
    bw.Flush() // 写出，输出1
}</pre>
<div class="blog_h3"><span class="graybg">缓冲读 </span></div>
<pre class="crayon-plain-tag">r := strings.NewReader("既然选择了远方，就应该风雨兼程")
br := bufio.NewReaderSize(r, 18)
rune, size, err := br.ReadRune()
if err == nil {
    fmt.Printf("Character: %c, Size: %v\n", rune, size) // Character: 既, Size: 3
}
buf := make([]byte, 18)
br.Read(buf)
fmt.Printf("String: %v", string(buf)) // String: 然选择了远
br.Discard(6)
rune, _, _ = br.ReadRune()
fmt.Printf("%c", rune) // 就</pre>
<div class="blog_h3"><span class="graybg">缓冲扫描 </span></div>
<pre class="crayon-plain-tag">func main() {
    // 扫描器可以方便进行扫描，例如逐行读取、逐字符读取
    bs := bufio.NewScanner(strings.NewReader("既然选择了远方，就应该风雨兼程"))
    // 每读取一个字符即停止
    bs.Split(bufio.ScanRunes)
    for bs.Scan() {
        // 以字符串形式返回读取到的内容
        fmt.Print(bs.Text())
    }
}</pre>
<div class="blog_h2"><span class="graybg">ioutil</span></div>
<p>包含一些工具函数</p>
<pre class="crayon-plain-tag">// 读取全部内容
bytes, err := ioutil.ReadAll(strings.NewReader("Hello darkness my old friend"))
if err == nil {
    fmt.Printf("%c", bytes)
}

// 读取目录的全部条目
infos, err := ioutil.ReadDir("/etc/mysql")
for _, info := range infos {
    // 打印目录中的内容
    fmt.Println(info.Name())
}

// 读取文件的内容并返回
const FNAME = "/etc/mysql/my.cnf"
content, _ := ioutil.ReadFile(FNAME)
// 写入文件内容
ioutil.WriteFile(FNAME, content, 0777)

// 创建临时文件和目录
dirName, _ := ioutil.TempDir("", "go")
fmt.Println(dirName) // 输出目录/tmp/go154074736，注意随机后缀</pre>
<div class="blog_h2"><span class="graybg">fmt</span></div>
<p>该包提供类似C语言风格的printf、scanf</p>
<div class="blog_h3"><span class="graybg">格式化动词</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 10%; text-align: center;">动词</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%v</td>
<td>值的默认形式</td>
</tr>
<tr>
<td>%#v</td>
<td>值的Go语法展现形式</td>
</tr>
<tr>
<td>%T</td>
<td>值类型的Go语法展现形式</td>
</tr>
<tr>
<td>%t</td>
<td>true或false</td>
</tr>
<tr>
<td>%%</td>
<td>转义%且不消耗参数</td>
</tr>
<tr>
<td colspan="2"><em>整数格式化</em></td>
</tr>
<tr>
<td>%b</td>
<td>二进制整数</td>
</tr>
<tr>
<td>%c</td>
<td>Unicode代码点对应的字符</td>
</tr>
<tr>
<td>%d</td>
<td>十进制</td>
</tr>
<tr>
<td>%o</td>
<td>八进制</td>
</tr>
<tr>
<td>%q</td>
<td>用引号包围的字符串形式，转义已经处理好</td>
</tr>
<tr>
<td>%x</td>
<td>十六进制a-f</td>
</tr>
<tr>
<td>%X</td>
<td>十六进制A-F</td>
</tr>
<tr>
<td>%U</td>
<td>Unicode格式</td>
</tr>
<tr>
<td colspan="2"><em>浮点数和复数</em></td>
</tr>
<tr>
<td>%e</td>
<td>科学计数法，使用e</td>
</tr>
<tr>
<td>%E</td>
<td>科学计数法，使用E</td>
</tr>
<tr>
<td>%f  %F</td>
<td>小数格式，示例：<pre class="crayon-plain-tag">fmt.Printf("%f",12.22) // 12.220000</pre> </td>
</tr>
<tr>
<td>%g  %G</td>
<td>对于大的指数使用%e %E，否则使用%f</td>
</tr>
<tr>
<td colspan="2"><em>字符串和字节切片</em></td>
</tr>
<tr>
<td>%s</td>
<td>未解释的原始字节</td>
</tr>
<tr>
<td>%q</td>
<td>引号包围的已经转义的字符串</td>
</tr>
<tr>
<td>%x  %X</td>
<td>十六进制表示，每个字节2字符</td>
</tr>
<tr>
<td colspan="2"><em>指针</em></td>
</tr>
<tr>
<td>%p</td>
<td>0x开头的十六进制</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><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>
</tr>
</thead>
<tbody>
<tr>
<td>结构</td>
<td>{field0 field1 ...}</td>
</tr>
<tr>
<td>数组和切片</td>
<td>[elem0 elem1 ...]</td>
</tr>
<tr>
<td>映射</td>
<td>map[key1:value1 key2:value2]</td>
</tr>
<tr>
<td>上述类型的指针</td>
<td>&amp;{}, &amp;[], &amp;map[]</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><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>
</tr>
</thead>
<tbody>
<tr>
<td>%f</td>
<td>默认宽度和精度</td>
</tr>
<tr>
<td>%9f</td>
<td>宽度为9，默认精度</td>
</tr>
<tr>
<td>%.2f</td>
<td>默认宽度，精度为2</td>
</tr>
<tr>
<td>%9.2f</td>
<td>宽度为9，精度为2</td>
</tr>
<tr>
<td>%9.f</td>
<td>宽度为9，精度为0</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">文件处理</span></div>
<div class="blog_h2"><span class="graybg">打开文件</span></div>
<pre class="crayon-plain-tag">// 打开并得到os.File对象
f, err := os.Open("/etc/rc.local")

// 关闭文件
defer f.Close()</pre>
<div class="blog_h2"><span class="graybg">读取文件</span></div>
<div class="blog_h3"><span class="graybg">读取到缓冲区</span></div>
<pre class="crayon-plain-tag"># 读取最多5字节到缓冲
b1 := make([]byte, 5)
# 返回实际读取的字节数
n1, err := f.Read(b1)</pre>
<div class="blog_h3"><span class="graybg">随机访问 </span></div>
<pre class="crayon-plain-tag">// 寻道到第6字节处，第二参数：0相对于文件头，1 相对于当前位置，2相对于文件尾
o2, err := f.Seek(6, 0)
b2 := make([]byte, 2) 
n2, err := f.Read(b2)

// Rewind
f.Seek(0, 0)</pre>
<div class="blog_h3"><span class="graybg">缓冲读</span></div>
<pre class="crayon-plain-tag">r4 := bufio.NewReader(f)
// 向前偷窥
b4, err := r4.Peek(5)</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/go-io-programming">Go语言IO编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/go-io-programming/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Python进行文本处理</title>
		<link>https://blog.gmem.cc/text-processing-with-python</link>
		<comments>https://blog.gmem.cc/text-processing-with-python#comments</comments>
		<pubDate>Fri, 06 Jan 2012 07:37:03 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[文本处理]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11459</guid>
		<description><![CDATA[<p>编程语言特性 字符串的表示 在Python2中，类型[crayon-69de22b373d0c688653909-i/] 和[crayon-69de22b373d10619783735-i/] 分别用于表示单字节字符串和Unicode字符串；在Python3中，所有字符串都使用Unicode表示，类型为str。 字符串可以使用单引号、双引号、三引号包围， 三引号中的字符串原样保留，可以方便的编写多行文本。 字符串的引号开始前，可以增加[crayon-69de22b373d12516776819-i/] 、[crayon-69de22b373d14785016946-i/] 修饰符，分别表示目标字符串是Unicode类型、不启用字符转义。 Python没有单独的字符类型，字符只是长度为1的字符串 切片运算符 在Python语言中，字符串属于（不可变）序列，支持有限的切片操作： [crayon-69de22b373d16549148642/] in运算符 在Python中，此操作符可以用于成员关系测试，或者迭代： [crayon-69de22b373d19992252981/] +和*运算符 适用于任意切片的运算符还包括[crayon-69de22b373d1b563583793-i/] 和[crayon-69de22b373d1d886304010-i/] ，分别可以用于连接、重复字符串： [crayon-69de22b373d20273130906/] 内置函数与方法 很多内置函数、方法可以用于操控字符串：  [crayon-69de22b373d22142048365/] 字符串格式化 Python支持两种风格的字符串格式化操作，包括旧式的[crayon-69de22b373d24349670626-i/] 操作符，以及新式的[crayon-69de22b373d27622258459-i/] 方法。 基于%操作符的格式化 <a class="read-more" href="https://blog.gmem.cc/text-processing-with-python">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/text-processing-with-python">使用Python进行文本处理</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_h2"><span class="graybg">编程语言特性</span></div>
<div class="blog_h3"><span class="graybg">字符串的表示</span></div>
<p>在Python2中，类型<pre class="crayon-plain-tag">str</pre> 和<pre class="crayon-plain-tag">unicode</pre> 分别用于表示单字节字符串和Unicode字符串；在Python3中，所有字符串都使用Unicode表示，类型为str。</p>
<p>字符串可以使用单引号、双引号、三引号包围， 三引号中的字符串原样保留，可以方便的编写多行文本。</p>
<p>字符串的引号开始前，可以增加<pre class="crayon-plain-tag">u</pre> 、<pre class="crayon-plain-tag">r</pre> 修饰符，分别表示目标字符串是Unicode类型、不启用字符转义。</p>
<p>Python没有单独的字符类型，字符只是长度为1的字符串</p>
<div class="blog_h3"><span class="graybg">切片运算符</span></div>
<p>在Python语言中，字符串属于<span style="background-color: #c0c0c0;">（不可变）序列</span>，支持有限的切片操作：</p>
<pre class="crayon-plain-tag">s = "一二三四五六七"
# 取子串，起始索引包含，结束索引不包含
assert s[1:3] == '二三'
assert s[-3:] == '五六七'
i = 3
assert s[:i] + s[i:] == s

# 带步进的切片，默认1，即下一个元素的索引比上一个大1
assert s[1:4:1] == '二三四'
assert s[1:4:2] == '二四'
assert s[1:4:3] == '二'
# 负数的步进，下面这个用于生成倒序的串
assert s[::-1] == '七六五四三二一'
assert s[4:1:-1] == '五四三'</pre>
<div class="blog_h3"><span class="graybg">in运算符</span></div>
<p>在Python中，此操作符可以用于成员关系测试，或者迭代：</p>
<pre class="crayon-plain-tag">s = '01234567'
assert '3' in s
for c in s: print(c)</pre>
<div class="blog_h3"><span class="graybg">+和*运算符</span></div>
<p>适用于任意切片的运算符还包括<pre class="crayon-plain-tag">+</pre> 和<pre class="crayon-plain-tag">*</pre> ，分别可以用于连接、重复字符串：</p>
<pre class="crayon-plain-tag">assert '123' + '4567' == '1234567'
assert '123' * 3 == '123123123'</pre>
<div class="blog_h2"><span class="graybg">内置函数与方法</span></div>
<p>很多内置函数、方法可以用于操控字符串： </p>
<pre class="crayon-plain-tag">s = "01234567"
# 计算序列长度
assert len(s) == 8
# 获取序列元素的最小值
assert min(s) == '0'
# 获取序列元素的最大值
assert max(s) == '7'

# 查找，获得子串的索引，两个方法类似，但是index()在无法子串不存在时抛出ValueError
assert s.index('67') == 6
assert s.find('67') == 6
# 查找，子串判断
assert s.startswith('012') and s.endswith('4567')
# 替换，注意字符串是不可变的，因此返回的是副本
assert s.replace('3', 'x') == "012x4567"
# 分割
assert s.split('3') == ['012', '4567']
# 连接
assert ','.join(['1', '2', '3']) == '1,2,3'
# 对齐和填充
assert s.ljust(10, '0') == '0123456700'
# 去除空白，左侧
assert s.lstrip('0') == '1234567'
# 特性检查：is*()方法
assert s.isalnum()  # 是否仅包含字母数字</pre>
<div id="format-string" class="blog_h2"><span class="graybg"><a id="format-string"></a>字符串格式化</span></div>
<p>Python支持两种风格的字符串格式化操作，包括旧式的<pre class="crayon-plain-tag">%</pre> 操作符，以及新式的<pre class="crayon-plain-tag">format()</pre> 方法。</p>
<div class="blog_h3"><span class="graybg">基于%操作符的格式化</span></div>
<p>可以使用<pre class="crayon-plain-tag">%</pre> 操作符来进行字符串格式化，<pre class="crayon-plain-tag">s % d</pre> 左侧为格式模板，右边为占位符元组或字典，这与C语言的sprintf()函数类似。一个简单的例子如下：</p>
<pre class="crayon-plain-tag">'%s %s' % ('one', 'two') 
'%(k1)s %(k2)s' % {'k1':'one','k2':'two'}</pre>
<p>格式模板由普通字符、转换说明符组成，<span style="background-color: #c0c0c0;">转换说明符以%开头</span>，具体含义如下：</p>
<table class="full-width" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px;"> 格式说明符</td>
<td>输出格式 </td>
</tr>
</thead>
<tbody>
<tr>
<td>d, i </td>
<td>十进制整数、长整数</td>
</tr>
<tr>
<td>u</td>
<td>无符号整数、长整数</td>
</tr>
<tr>
<td>o</td>
<td>八进制整数、长整数</td>
</tr>
<tr>
<td>x</td>
<td>十六进制整数、长整数</td>
</tr>
<tr>
<td>X</td>
<td>十六进制整数（大写字母）</td>
</tr>
<tr>
<td>f</td>
<td>浮点数，如[-]m.dddddd</td>
</tr>
<tr>
<td>e</td>
<td>浮点数，如[-]m.dddddde+xx</td>
</tr>
<tr>
<td>E</td>
<td>浮点数，如[-]m.ddddddE+xx</td>
</tr>
<tr>
<td>g, G</td>
<td>指数小于-4或更高精度时使用%e或%E,否则格式化为浮点数<br />保留N位有效数字，N为下表的修饰符字段，采取四舍五入方式</td>
</tr>
<tr>
<td>s</td>
<td>字符串或任意对象。格式化代码使用str()生成字符串</td>
</tr>
<tr>
<td>r</td>
<td>同repr ()生成的字符串</td>
</tr>
<tr>
<td>c</td>
<td>单个字符</td>
</tr>
<tr>
<td>%</td>
<td>字面值%</td>
</tr>
</tbody>
</table>
<p>在字符%与上表列出的转换字符之间，可以按顺序出现以下修饰符：</p>
<table class="full-width" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px;"> 修饰符</td>
<td> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>(KEY)</td>
<td>表示从字典中取值的键</td>
</tr>
<tr>
<td>-</td>
<td>左对齐标志位，默认右对齐</td>
</tr>
<tr>
<td>+</td>
<td>保留数字的符号（即使正数）</td>
</tr>
<tr>
<td>0</td>
<td>表示一个零填充</td>
</tr>
<tr>
<td>M</td>
<td>表示输出的最小宽度</td>
</tr>
<tr>
<td>.</td>
<td>小数点，用于按精度分割字段宽度</td>
</tr>
<tr>
<td>N</td>
<td>要打印字符串的最大字符数、浮点数小数点后的位数、整数的最小位数</td>
</tr>
<tr>
<td>*</td>
<td>从元组中读取字段的宽度数字</td>
</tr>
</tbody>
</table>
<pre class="crayon-plain-tag">a = 42
b = 13.142783 c = "hello"
d = {'x':13, 'y':l.54321, 'z':'world'} 
e = 5628398123741234

r = "a is %d" % a                     # r = "a is 42"
r = "%10d %f" %(a,b)                  # r = "        42 13.142783"    整数宽度10，右对齐
r = "%+010d %E" %(a,b)                # r = "+000000042 1.314278E+01" 整数宽度10，右对齐，不足补0
r = "%(x)-10d %(y)0.3g" % d           # r = "13          1.54"        从字典d里取出值并对其执行转换
r = "%0.4s %s" %(c, d['z'])           # r = "hell world"              第一个字符串最大打印长度为4
r = "%*.*f" % (5,3,b)                 # r = "13.143"                  元组的前两项作为格式字符串的组成部分

#注意，格式化字符串与字典一起使用，行为类似于字符串插值：
p = {'age' : 28, 'name' : 'Alex'}
r = "%(name)s's age is %(age)d" % p   # r = "Alex's age is 28"</pre>
<div class="blog_h3"><span class="graybg">基于string.format()方法的格式化</span></div>
<p>通过调用作为模板的字符串的format()是Python新式的格式化机制。模板由普通字符和占位符组成，可以使用<pre class="crayon-plain-tag">{place:format_spec}</pre> 的形式来声明一个占位符，它包括两个部分：</p>
<ol>
<li>place，位置，可以是数字表示的位置参数、标识符表示的命名参数。默认情况下Python使用<pre class="crayon-plain-tag">__format__()</pre> 得到对象的文本表示，要改变此行为，可以扩展place为<pre class="crayon-plain-tag">place!type</pre> 形式。其中type可以取值s、r、a，分别调用<pre class="crayon-plain-tag">__str__()</pre> 、<pre class="crayon-plain-tag">__repr__()</pre> 和<pre class="crayon-plain-tag">ascii()</pre> 完成文本转换，a仅在Python3中被支持</li>
<li>format_spec，格式说明符，形式为：<pre class="crayon-plain-tag">[[fill]align][sign][#][0][width][,][.precision][type]</pre> ，各字段说明如下：</li>
</ol>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 100px;">fill</td>
<td>当指定align时，可以指定该字段，用于补白，默认空格</td>
</tr>
<tr>
<td>align</td>
<td>
<p>对齐方式：<pre class="crayon-plain-tag">&lt;</pre> 表示左对齐，<pre class="crayon-plain-tag">&gt;</pre> 表示右对齐，<pre class="crayon-plain-tag">=</pre> 表示在数字左侧补白，<pre class="crayon-plain-tag">^</pre> 表示居中对齐</p>
</td>
</tr>
<tr>
<td>sign</td>
<td>符号，可以取值<pre class="crayon-plain-tag">+</pre> 、<pre class="crayon-plain-tag">-</pre> 和空格</td>
</tr>
<tr>
<td>#</td>
<td>仅用于二、八、十六进制，如果出现此符号，输出分别添加前缀<pre class="crayon-plain-tag">0b</pre> 、<pre class="crayon-plain-tag">0o</pre> 、<pre class="crayon-plain-tag">0x</pre> </td>
</tr>
<tr>
<td>,</td>
<td>千位分隔符</td>
</tr>
<tr>
<td>width</td>
<td>指定最小宽度</td>
</tr>
<tr>
<td>precision</td>
<td>使用f或者F格式化时，小数的位数<br />使用g或者G格式化时，数字的位数<br />对于非数字，表示最大字符数<br />不可用于整数</td>
</tr>
<tr>
<td>type</td>
<td>格式化为什么类型，参考上文“格式说明符”</td>
</tr>
</tbody>
</table>
<p>代码示例：</p>
<pre class="crayon-plain-tag"># ###  参数占位符  ###
# 位置参数
'{0}, {1}, {2}'.format('a', 'b', 'c')  # 'a, b, c'
'{2}, {1}, {0}'.format('a', 'b', 'c')  # 'c, b, a'
'{}, {}, {}'.format('a', 'b', 'c')  # 'a, b, c' 要求2.7+
'Hello {0}'.format('Alex') == 'Hello Alex'
# 命名参数
'Coordinates: {latitude}, {longitude}'.format(latitude = '37.24N', longitude = '-115.81W')
'Hello {name}'.format(name = 'Alex') == 'Hello Alex'
# 访问对象属性
person = lambda: None
person.greeting = 'Hello'
person.name = 'Alex'
assert '{0.greeting} {0.name}'.format(person) == 'Hello Alex'
# 访问序列的元素
coord = (3, 5)
'X: {0[0]};  Y: {0[1]}'.format(coord)

# ###  格式控制  ###
# 对齐和填充
'{:&gt;30}'.format('right aligned')  # '                 right aligned'
'{:*^30}'.format('centered')  # '***********centered***********'
# 浮点数
'{:+f}; {:+f}'.format(3.14, -6.02)  # '+3.140000; -6.020000'
# 千分位
'{:,}'.format(1234567890)  # '1,234,567,890'

# 格式化时间
import datetime
d = datetime.datetime(2010, 7, 4, 12, 15, 58)
'{:%Y-%m-%d %H:%M:%S}'.format(d)  # '2010-07-04 12:15:58'</pre>
<div class="blog_h2"><span class="graybg">正则表达式</span></div>
<p>re模块可以用于正则表达式的处理。</p>
<div class="blog_h3"><span class="graybg">正则式字符序列</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">字符序列</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>text</td>
<td>匹配文字字符串text</td>
</tr>
<tr>
<td>.</td>
<td>匹配任何字符串，但换行符除外</td>
</tr>
<tr>
<td>^</td>
<td>匹配字符串的开始</td>
</tr>
<tr>
<td>$</td>
<td>匹配字符串的结束</td>
</tr>
<tr>
<td>*</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能多的副本</td>
</tr>
<tr>
<td>+</td>
<td>匹配前面表达式的1个或多个副本，匹配尽可能多的副本</td>
</tr>
<tr>
<td>?</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能多的副本</td>
</tr>
<tr>
<td>*?</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能少的副本</td>
</tr>
<tr>
<td>+?</td>
<td>匹配前面表达式的1个或多个副本，匹配尽可能少的副本</td>
</tr>
<tr>
<td>??</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能少的副本</td>
</tr>
<tr>
<td>{m}</td>
<td>准确匹配前面表达式的m个副本</td>
</tr>
<tr>
<td>{m, n}</td>
<td>匹配前面表达式的第m到n个副本，尽可能匹配多的副本</td>
</tr>
<tr>
<td>{m, n}?</td>
<td>匹配前面表达式的第m到n个副本，尽可能匹配少的副本</td>
</tr>
<tr>
<td>[...]</td>
<td>匹配一组字符，如<pre class="crayon-plain-tag">r'[abcde]'</pre> 或者<pre class="crayon-plain-tag">r'a-zA-Z'</pre> </td>
</tr>
<tr>
<td>[^...]</td>
<td>匹配集合中未包含的字符，如<pre class="crayon-plain-tag">r'[^0-9]'</pre> </td>
</tr>
<tr>
<td>A|B</td>
<td>匹配A或者B，A和B都是正则式</td>
</tr>
<tr>
<td>(...)</td>
<td>匹配圆括号中的正则表达式（圆括号中的内容为一个分组）并保存匹配的子字符串<br />在匹配时，分组中的内容可以使用所获得的<pre class="crayon-plain-tag">MatchObject</pre> 对象的<pre class="crayon-plain-tag">group()</pre> 方法获取</td>
</tr>
<tr>
<td>(?aiLmsux)</td>
<td>把字符解释为对应的标记位，例如i表示无视大小写</td>
</tr>
<tr>
<td>(?:...)</td>
<td>匹配圆括号中的正则表达式，但丢弃匹配的子字符串</td>
</tr>
<tr>
<td>(?P&lt;name&gt;...)</td>
<td>匹配圆括号中的正则表达式并创建一个命名（named）分组。分组名称必须是有效的Python标识符</td>
</tr>
<tr>
<td>(?P=name)</td>
<td>引用命名分组：匹配一个前面指定的分组所匹配的文本</td>
</tr>
<tr>
<td>(?#...)</td>
<td>一个注释。圆括号中的内容将被忽略</td>
</tr>
<tr>
<td>(?=...)</td>
<td>只有在括号中的模式匹配时，才匹配前面的表达式。例如<pre class="crayon-plain-tag">'Hello(?=World)'</pre> 只有在<pre class="crayon-plain-tag">World</pre> 匹配时才匹配<pre class="crayon-plain-tag">Hello</pre> </td>
</tr>
<tr>
<td>(?!...)</td>
<td>只有在括号中的模式不匹配时，才匹配前面的表达式</td>
</tr>
<tr>
<td>(?&lt;=...)</td>
<td>如果括号后面的表达式前面的值与括号中的模式匹配，则匹配该表达式。例如，只有当<pre class="crayon-plain-tag">'def'</pre> 前面是<pre class="crayon-plain-tag">'abc'</pre> 时，<pre class="crayon-plain-tag">r'(?&lt;=abc)def'</pre> 才会与它匹配</td>
</tr>
<tr>
<td>(?&lt;!...)</td>
<td>如果括号后面的表达式前面的值与括号中的模式不匹配，则匹配该表达式</td>
</tr>
<tr>
<td>\number</td>
<td>引用序号分组：匹配与前面的组编号匹配的文本。组编号范围为1到99，从左侧开始为组编号</td>
</tr>
<tr>
<td>\A</td>
<td>仅匹配字符串的开始标志</td>
</tr>
<tr>
<td>\b</td>
<td>匹配单词开始或结尾处的空字符串。单词是一个字母数字混合的字符序列，以空格或任何其他非字母数字字符结束</td>
</tr>
<tr>
<td>\B</td>
<td>匹配不在单词开始或结尾处的空字符串</td>
</tr>
<tr>
<td>\d</td>
<td>匹配任何十进制数。等同于<pre class="crayon-plain-tag">r'[0-9]'</pre> </td>
</tr>
<tr>
<td>\D</td>
<td>匹配任何非数字字符。等同于<pre class="crayon-plain-tag">r'[^0-9]'</pre> </td>
</tr>
<tr>
<td>\s</td>
<td>匹配任何空格字符。等同于<pre class="crayon-plain-tag">r'[\t\n\r\f\v]</pre> </td>
</tr>
<tr>
<td>\S</td>
<td>匹配任何非空格字符，等同于<pre class="crayon-plain-tag">r'[^\t\n\r\f\v]</pre> </td>
</tr>
<tr>
<td>\w</td>
<td>匹配任何字母数字字符</td>
</tr>
<tr>
<td>\W</td>
<td>匹配\w定义的集合中不包含的字符</td>
</tr>
<tr>
<td>\z</td>
<td>仅匹配字符串的结束标志</td>
</tr>
<tr>
<td>\\</td>
<td>匹配反斜杠本身</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"> 应用举例</span></div>
<pre class="crayon-plain-tag">import re
#获取字符串中与模式匹配的所有不重叠值，包括空匹配
#如果模式包含分组，将返回与分组匹配的文本列表，如果包含多个分组，则前述列表的每一项都是一个元组
#finditer与之类似，但是返回的是MatchObject的迭代器
#(\D+)(\d+) 结果：[('a', '0'), ('b', '12'), ('c', '345'), ('d', '6789')]
#(\D+)\d+   结果：['a', 'b', 'c', 'd']
#\D+\d+     结果：['a0', 'b12', 'c345', 'd6789']
m = re.findall(r'\D+\d+', 'a0b12c345d6789', re.IGNORECASE)  # 由于正则式特殊字符很多，故一般使用“原始字符串”
print m

r = re.compile(r'\D+\d+', re.IGNORECASE)       #编译得到一个正则式对象
#search()、match()返回MatcherObject对象


s = 'Iggle Piggle and Makka    Pakka are in the night garden'
# match、search等方法返回MatchObject：
# group(n)：返回匹配中第n个分组，如果n==0或者为空，返回整个匹配


# match函数从字符串起始处搜索正则式的匹配，找不到返回None
# 贪婪匹配
print re.match( r'.*ggle', s , re.IGNORECASE ).group()  # Iggle Piggle
# 非贪婪匹配
m = re.match( r'.*?(ggle)', s , re.IGNORECASE )
print m.group()  # Iggle
print m.group( 1 )  # ggle 返回匹配中的第一个分组

# search函数与match类似，但是不限定匹配从字符串首部开始
print re.search( r'.akka', s , re.IGNORECASE ).group()  # Makka

# sub(pattern, repl, string, count, flags) 函数用于字符串替换
# pattern 匹配模式 repl替换文本或回调 string被处理字符串 count需要处理的匹配个数
# 这里使用基于序号的分组，()界定了分组范围，分组可以嵌套：
# (M(\w+))是第一个分组，(\w+)是第二个分组，(P\2)是第三个分组
# repl可以指定转移字符，\g&lt;name&gt;用于引用匹配的正则式分组
print re.sub( r'(M(\w+))\s+(P\2)', '\g&lt;1&gt; \g&lt;3&gt;', s )  # 去掉Makka    Pakka之间多于的空格
# 命名分组，使用(?P&lt;GRP_NAME&gt;)定义命名分组，使用(?P=GRP_NAME)引用命名分组
print re.sub( r'(M(?P&lt;grp1&gt;\w+))\s+(P(?P=grp1))', 'M\g&lt;grp1&gt; \g&lt;3&gt;', s )
# repl还可以是函数
print re.sub( r'(M(?P&lt;grp1&gt;\w+))\s+(P(?P=grp1))', lambda match : match.group().upper(), s )</pre>
<div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">not all arguments converted during string formatting</span></div>
<p>原因：进行字符串格式化时，如果%后面的参数时元组，那么必须对所有元组成员进行格式化，也就是说，前面的<span style="background-color: #c0c0c0;">占位符数量要和元组长度一致</span>。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/text-processing-with-python">使用Python进行文本处理</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/text-processing-with-python/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用C语言进行文本处理</title>
		<link>https://blog.gmem.cc/text-processing-with-c</link>
		<comments>https://blog.gmem.cc/text-processing-with-c#comments</comments>
		<pubDate>Tue, 14 Jun 2011 14:48:12 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[文本处理]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11971</guid>
		<description><![CDATA[<p>字符集问题 字符集（Charset）、代码页（Code page）、编码方式（Encoding）这三个术语常常描述一件事情——如何把字符存储为二进制形式（字节）。 严格的讲，字符集是字符的集合，编码方式则用于确定某个字符集中的字符如何编码（为字节），但是对于ASCII、GB 2312、Big5、GBK、GB 18030之类的遗留方案来说一种字符集只有一种编码方式，这导致某些时候术语字符集、编码方式被混用。而Unicode是严格区分字符集、编码的，Unicode字符集有 UTF-8、UTF-16、UTF-32等多种编码方式。 微软称当前Locale对应的字符集为ANSI，和ASCII没有关系，对于简体中文的Windows操作系统，ANSI通常就是GBK。 进行文本处理时，编码方式常常是令人头疼的问题。相比起其它语言，C/C++的编码方式问题比较复杂。造成这种复杂性的原因包括： 对于编码方式缺乏统一规范，依赖于编译器、操作系统 构建出的二进制可执行文件丢失字符串（char*）编码方式信息 使用C/C++处理字符串时，要注意四个层面的字符集（表格出现的字符集可以理解为编码方式）： 字符集 说明 源代码字符集 即作为编译输入的C/C++源代码文件使用的字符集。编译器必须能够正确的识别源文件的编码方式才能读取并处理之 对于GCC编译器，也称为输入字符集（Input charset）。源文件默认编码方式取决于编译器被调用时[crayon-69de22b374d47173724957-i/] 、[crayon-69de22b374d4b219633460-i/] 、[crayon-69de22b374d4e683017007-i/] 等Locale相关环境变量，你也可以用编译器选项-finput-charset覆盖Locale 很多使用GCC或者其衍生工具条链（MinGW）的IDE，例如CLion、Eclipse，默认源代码使用UTF-8字符集Visual Studio在简体中文的Windows下使用GBK字符集 编译器内部字符集 对于GCC编译器，也称为源字符集（Source <a class="read-more" href="https://blog.gmem.cc/text-processing-with-c">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/text-processing-with-c">使用C语言进行文本处理</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">字符集问题</span></div>
<p>字符集（Charset）、代码页（Code page）、编码方式（Encoding）这三个术语常常描述一件事情——如何把字符存储为二进制形式（字节）。</p>
<p>严格的讲，字符集是字符的集合，编码方式则用于确定某个字符集中的字符如何编码（为字节），但是对于ASCII、GB 2312、Big5、GBK、GB 18030之类的<span style="background-color: #c0c0c0;">遗留方案来说一种字符集只有一种编码方式</span>，这导致某些时候术语字符集、编码方式被混用。而<span style="background-color: #c0c0c0;">Unicode是严格区分</span>字符集、编码的，Unicode字符集有 UTF-8、UTF-16、UTF-32等多种编码方式。</p>
<p>微软称当前<span style="background-color: #c0c0c0;">Locale对应的字符集为<strong><span style="color: #ffffff; background-color: #ff0000;">ANSI</span></strong></span>，和<strong><span style="color: #ffffff; background-color: #ff0000;">ASCII</span></strong>没有关系，对于简体中文的Windows操作系统，ANSI通常就是GBK。</p>
<p>进行文本处理时，编码方式常常是令人头疼的问题。相比起其它语言，C/C++的编码方式问题比较复杂。造成这种复杂性的原因包括：</p>
<ol>
<li>对于编码方式缺乏统一规范，依赖于编译器、操作系统</li>
<li>构建出的二进制可执行文件丢失字符串（char*）编码方式信息</li>
</ol>
<p>使用C/C++处理字符串时，要注意四个层面的字符集（表格出现的字符集可以理解为编码方式）：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">字符集</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>源代码字符集</td>
<td>
<p>即作为编译输入的C/C++源代码文件使用的字符集。编译器必须能够<span style="background-color: #c0c0c0;">正确的识别源文件的编码方式才能读取并处理之</span></p>
<p>对于GCC编译器，也称为输入字符集（Input charset）。源文件默认编码方式取决于<span style="background-color: #c0c0c0;">编译器被调用时</span><pre class="crayon-plain-tag">LC_ALL</pre> 、<pre class="crayon-plain-tag">LC_*</pre> 、<pre class="crayon-plain-tag">LANG</pre> 等Locale相关环境变量，你也可以用编译器选项-finput-charset覆盖Locale</p>
<p>很多使用GCC或者其衍生工具条链（MinGW）的IDE，例如CLion、Eclipse，默认源代码使用UTF-8字符集<br />Visual Studio在简体中文的Windows下使用GBK字符集</p>
</td>
</tr>
<tr>
<td>编译器内部字符集</td>
<td>对于GCC编译器，也称为源字符集（Source charset）。GCC内部使用UTF-8编码方式</td>
</tr>
<tr>
<td>执行字符集</td>
<td>
<p>即Execution charset，二进制可执行文件中的字符串、字符的编码方式。这个编码影响二进制文件的尺寸，例如多字节字符串<pre class="crayon-plain-tag">char* str = "你好"</pre> 使用GBK时<pre class="crayon-plain-tag">strlen()</pre> 返回值4，使用UTF-8时返回6</p>
<p>对于GCC编译器，有两个选项控制执行字符集：<br />-fexec-charset，决定字符串（包括多字节字符）、字符的编码方式，默认UTF-8<br />-fwide-exec-charset，决定宽字符串、宽字符的编码方式，默认UTF-16或者UTF-32，一般和<pre class="crayon-plain-tag">wchar_t</pre> 宽度一致</p>
</td>
</tr>
<tr>
<td>控制台字符集</td>
<td>
<p>程序使用<pre class="crayon-plain-tag">printf()</pre> 等函数打印字符到控制台上，用户才能看到。控制台也需要知道自己打印的内容是什么编码方式，否则会显示为<span style="background-color: #c0c0c0;">乱码</span></p>
<p>&nbsp;</p>
<p>对于字符串，<pre class="crayon-plain-tag">printf()</pre> 仅仅是简单的读取可执行文件中的<span style="background-color: #c0c0c0;">字节流</span>，并打印到输出流中，这要求你保证<span style="background-color: #c0c0c0;">执行字符集和控制台字符集一致或兼容，</span>否则乱码</p>
<p>&nbsp;</p>
<p>对于宽字符串，<pre class="crayon-plain-tag">wprintf()</pre> 需要读取宽字符串的编码（UTF-16/UTF-32），并将其<span style="background-color: #c0c0c0;">转化为Locale指定的编码方式</span>，然后打印到输出流。那么C程序如何得到Locale呢？</p>
<p>在所有C程序main函数执行前，它会调用<pre class="crayon-plain-tag">setlocale(LC_ALL,"C");</pre> ，这个所谓的C是所有C程序使用的最小化的Locale。这个C仅支持少量字符，肯定会导致中文乱码。要解决此问题，可以在程序开始处手工调用<pre class="crayon-plain-tag">setlocale(LC_CTYPE, "")</pre> ，该调用后程序使用<span style="background-color: #c0c0c0;">系统默认Locale来处理C字符串（CTYPE）</span>，其编码方式和控制台一般是一致的，因而避免了乱码</p>
<p>&nbsp;</p>
<p>控制台使用的编码方式取决于软件或者OS，例如<span style="background-color: #c0c0c0;">简体中文</span>Windows操作系统中<span style="background-color: #c0c0c0;">cmd.exe</span>使用的代码页936类似于字符集<span style="background-color: #c0c0c0;">GBK</span>。你可以使用命令调整代码页：</p>
<pre class="crayon-plain-tag">rem 显示当前代码页（和字符集是类似的概念）
CHCP
rem 936是简体中文代码页，最初和GB2312一样，后来包含大部分的GBK字符
Active code page: 936
rem 切换为UTF-8代码页
CHCP 65001</pre>
<p>Windows全局的Locale设置在控制面板中进行</p>
<p>Linux下可以设置<a href="/linux-faq#envars-locale">Locale相关环境变量</a>，来改变Terminal使用的字符集</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">C标准库</span></div>
<div class="blog_h2"><span class="graybg"><a id="std-ctype"></a>字符测试ctype.h</span></div>
<p>该头文件主要提供两类重要的函数：</p>
<ol>
<li>字符类别测试</li>
<li>字符大小转换</li>
</ol>
<p>该库提供的函数中都以int类型为参数，并返回一个int类型的值。实参类型应该隐式/显式转换为int类型</p>
<p>函数列表如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><pre class="crayon-plain-tag">int isalnum(int c)</pre> </td>
<td>判断是否是字母或数字</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isalpha(int c)</pre> </td>
<td>判断是否是字母</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int iscntrl(int c)</pre> </td>
<td>判断是否是控制字符</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isdigit(int c)</pre> </td>
<td>判断是否是数字</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isgraph(int c)</pre> </td>
<td>判断是否是可显示字符</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int islower(int c)</pre> </td>
<td>判断是否是小写字母</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isupper(int c)</pre> </td>
<td>判断是否是大写字母</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isprint(int c)</pre> </td>
<td> 判断是否是可显示字符</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int ispunct(int c)</pre> </td>
<td>判断是否是标点字符</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isspace(int c)</pre> </td>
<td>判断是否是空白字符</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int isxdigit(int c)</pre> </td>
<td>判断字符是否为16进制</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int tolower(int c)</pre> </td>
<td>转换为小写字母</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">int toupper(int c)</pre> </td>
<td>转换为大写字母</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg"><a id="std-string"></a>字符串函数string.h</span></div>
<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><pre class="crayon-plain-tag">memchr()</pre></td>
<td>
<p>在某一内存范围中查找一特定字符：</p>
<pre class="crayon-plain-tag">/**
 * 扫描s所指内存的前n个字节，来寻找c出现的第一个位置
 * c和s所指内存的字节，均被解释为unsigned char
 * 返回指向匹配字节的指针，如果找不到返回NULL，rawmemchr()在找不到的情况下返回值未定义
 */
void *memchr(const void *s, int c, size_t n);  // 正向搜索
// 下面两个是GNU扩展
void *memrchr(const void *s, int c, size_t n); // 反向搜索
void *rawmemchr(const void *s, int c); // 正向搜索，不限制字节数</pre>
<p>举例：</p>
<p><pre class="crayon-plain-tag">const char *mem = "0123456789";
char *p3 = (char *) memchr( mem, '3', strlen( mem ));
assert( p3 - mem == 3 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">memcmp()</pre></td>
<td>
<p>比较内存内容：</p>
<pre class="crayon-plain-tag">/**
 * 比较s1、s2两块内存区域的前n个字节
 * 当s1小于、等于、大于s2时分别返回负数、0、正数
 */
int memcmp(const void *s1, const void *s2, size_t n);</pre>
<p>举例：</p>
<p><pre class="crayon-plain-tag">char *s1 = "abcdew";
char *s2 = "abcdez";
assert( memcmp( s1, s2, strlen( s1 ) - 1 ) == 0 );
assert( memcmp( s1, s2, strlen( s1 )) &lt; 0 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">memcpy()</pre> </td>
<td>
<p>拷贝内存内容，两个内存区域必须不重叠：</p>
<p><pre class="crayon-plain-tag">/**
 * 从src拷贝n个字节到dest
 * 返回指向dest的指针
 */
void *memcpy( void *dest, const void *src, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">memmove()</pre> </td>
<td>
<p>移动内存内容，两个内存区域可以重叠：</p>
<p><pre class="crayon-plain-tag">/**
 * 类似于memcpy，效果上相当于把src先拷贝到临时内存中，然后覆盖到dest
 */
void *memmove(void *dest, const void *src, size_t n);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">memset()</pre> </td>
<td>
<p>将一段内存空间填入某值，常用于内存清零：</p>
<p><pre class="crayon-plain-tag">void *memset(void *s, int c, size_t n); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strcat()</pre> </td>
<td>
<p>连接两字符串：</p>
<pre class="crayon-plain-tag">/**
 * 将src附加到dest后面，覆盖dest结尾的\0，并在连接后再次添加一个\0
 * 两个字符串不得重叠，并且dest必须由足够的空间来存放结果，如果dest空间不足则程序的行为无法预测
 */
char *strcat( char *dest, const char *src );

/**
 * 连接两字符串，与strcat类似，但是：
 *     最多使用src的n个字节
 *     如果src大于n字节，则它不需要以\0结束
 */
char *strncat( char *dest, const char *src, size_t n );</pre>
<p>举例：</p>
<p><pre class="crayon-plain-tag">char *dest = malloc( 100 );
strcat( dest, "Hello" );
strcat( dest, " World" );
assert( strcmp( dest, "Hello World" ) == 0 ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strchr()</pre> </td>
<td>
<p>在字符串中定位单个字符：</p>
<p><pre class="crayon-plain-tag">// 返回s中第一个c的指针，如果找不到返回NULL
char *strchr(const char *s, int c);
// 返回s中最后一个c的指针
char *strrchr(const char *s, int c);
// 与strchr()类似，但是在找不到的时候，返回s结尾的\n的指针而不是NULL
char *strchrnul(const char *s, int c);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strcmp()</pre> </td>
<td>
<p>比较字符串：</p>
<p><pre class="crayon-plain-tag">//比较s1、s2，在s1小于、等于、大于s2时分别返回负数、0、正数
int strcmp( const char *s1, const char *s2 );
//比较s1、s2的前n个字节
int strncmp( const char *s1, const char *s2, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strcoll()</pre> </td>
<td>
<p>使用当前Locale比较两个字符串，结果受<pre class="crayon-plain-tag">LC_COLLATE</pre> 影响：</p>
<p><pre class="crayon-plain-tag">int strcoll(const char *s1, const char *s2);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strcpy()</pre> </td>
<td>
<p>拷贝字符串：</p>
<p><pre class="crayon-plain-tag">/**
 * 拷贝src，包含结尾的\0，到dest。dest必须足够大，两个字符串不得重叠
 *
 */
char *strcpy( char *dest, const char *src );
// 类似，但是最多拷贝n字节，注意，该函数可能导致结尾的\0丢失
char *strncpy( char *dest, const char *src, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strspn()</pre> </td>
<td>
<p>依据一系列字节来搜索字符串：</p>
<pre class="crayon-plain-tag">/**
 * 搜索s，直到出现不在accept中的字节
 * 返回从头开始，一直处于accept中的字符的个数
 */
size_t strspn( const char *s, const char *accept );
/**
 * 搜索s，知道出现在reject中的字节
 * 返回从头开始，第一个在reject中的字节之前的字节总数
 */
size_t strcspn( const char *s, const char *reject );</pre>
<p> 举例：</p>
<p><pre class="crayon-plain-tag">assert( strspn( "54213zyx", "1234567890" ) == 5 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strerror()</pre> </td>
<td>
<p>返回错误原因的描述字符串，举例：</p>
<p><pre class="crayon-plain-tag">printf( strerror( 2 ));//No such file or directory</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strlen()</pre> </td>
<td>
<p>计算字符串长度，得到的是<span style="background-color: #c0c0c0;">字节数量</span>：</p>
<p><pre class="crayon-plain-tag">assert( strlen( "nh" ) == 2 );
assert( strlen( "你好" ) == 6 ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strpbrk()</pre> </td>
<td>
<p>查找字符串中第一个出现的指定字节：</p>
<p><pre class="crayon-plain-tag">/**
 * 搜索s，直到出现accept中的任何一个字节
 * 返回第一个accept中的字节，如果找不到返回NULL
 */
char *strpbrk( const char *s, const char *accept );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strstr()</pre> </td>
<td>
<p>在一字符串中查找指定的子串：</p>
<p><pre class="crayon-plain-tag">/**
 * 返回haystack中第一次出现needle的、needle的起始字节的指针，结尾的\0不参与比较
 * 如果子串找不到返回NULL
 */
char *strstr(const char *haystack, const char *needle);
// GNU扩展，与上面类似，但是不区分大小写
char *strcasestr(const char *haystack, const char *needle);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strtok()</pre> </td>
<td>
<p>将字符串分割为0个或多个非空字符串：</p>
<pre class="crayon-plain-tag">/**
 * 第一次调用时，传递待分割的字符串到str，后续分割同一字符串的操作，必须传递NULL给str
 * delim是一系列作为分隔符的字节，后续调用可以改变delim
 * 每次调用的返回值是指向分割得到的子串的指针，没有更多的记号时返回NULL
 */
char *strtok( char *str, const char *delim );
/**
 * strtok()的可重入版本
 * saveptr是供函数内部使用的一个指针，保存分隔上下文
 */
char *strtok_r( char *str, const char *delim, char **saveptr );</pre>
<p> 举例：</p>
<pre class="crayon-plain-tag">char str[] = "123:456,789....0";
char *token;
char *ctx = str;
while ( token = strtok_r( ctx, ":,.", &amp;ctx )) {
    printf( "%s|", token );
}
//打印123|456|789|0| 可以看到....中间的不作为子串</pre>
<p> 注意，该函数修改了str的内容：它把分隔字符替换为\0</p>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">strxfrm()</pre> </td>
<td>
<p>拷贝字符串：</p>
<pre class="crayon-plain-tag">/**
 * 拷贝src的n个字节到数组dest中，返回拷贝后的字符串的长度
 * 如果返回值大于等于n则dest数组的内容是不确定的
 */
size_t strxfrm( char *dest, const char *src, size_t n );</pre>
<p> 举例：</p>
<pre class="crayon-plain-tag">char *source = "1234567890";
char des[100];
size_t len = strxfrm( des, source, 50 );
assert( len == 10 &amp;&amp; strcmp( des, source ) == 0 );
memset( des, 0, 100 );
len = strxfrm( des, source, 5 );
assert( len == 10 &amp;&amp; strcmp( des, "12345" ) == 0 );</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg"><a id="std-wchar"></a>宽字符串函数wchar.h</span></div>
<p>所谓宽字符，是指使用多个字节表示的字符。宽字符类型<span style="background-color: #c0c0c0;">具有固定宽度，但是宽度取决于平台（编译器），这意味着使用宽字符会导致可移植性问题</span>。宽字符在Linux系统中使用的不多。
<p>在2011年的C和C++标准中固定宽度的字符类型<pre class="crayon-plain-tag">char16_t</pre> 、<pre class="crayon-plain-tag">char32_t</pre> 被引入，用来表示无歧义的16位、32位的Unicode转换格式（UTF）。</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><pre class="crayon-plain-tag">btowc()</pre> </td>
<td>
<p>把单个字节转换为宽字符：</p>
<pre class="crayon-plain-tag">/**
 * 执行单字节字符到宽字符的转换
 * @param c 单字节字符
 * @return 转换c所代表的字符的宽字符表示
 *         如果c为EOF或者不是有效单字节字符，返回WEOF
 */
wint_t btowc( int c ); </pre>
<p>避免使用该函数，因为其无法处理带有状态的编码方式。使用<pre class="crayon-plain-tag">mbtowc()</pre> 或者线程安全的<pre class="crayon-plain-tag">mbrtowc()</pre> 代替之：</p>
<pre class="crayon-plain-tag">/**
 * 从多字节序列s中抽取出一个宽字符，该函数最多检查s的n个字节，并把转换后得到的宽
 * 字符存放在*pwc中，返回从s中消费掉的字节数。该函数在内部维护一个偏移状态（Shift state）导致其线程不安全
 * 
 * 该函数需要知道s的编码方式，这是由当前Locale的LC_CTYPE目录决定的，因此调用该函
 * 数前你可能需要调用setlocale来设置多字节使用的编码方式
 */
int mbtowc( wchar_t *pwc, const char *s, size_t n );</pre>
<p>举例： </p>
<p><pre class="crayon-plain-tag">setlocale( LC_ALL, "" );
char *str = "你好，世界";
wchar_t wc = 0;
// MB_CUR_MAX 当前Locale下多字节字符占据的最大字节数
int len = mblen( str, MB_CUR_MAX);
str += mbtowc( &amp;wc, str, len * strlen( str ));
wprintf( L"%lc \n", wc ); //你
str += mbtowc( &amp;wc, str, len * strlen( str ));
wprintf( L"%lc \n", wc ); //好</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wctob()</pre> </td>
<td>
<p>执行宽字符到单字节字符的转换，避免使用该函数，使用<pre class="crayon-plain-tag">wctomb()</pre> 代替之：</p>
<pre class="crayon-plain-tag">/**
 * 转换宽字符wc为多字节序列，存放到s中，程序员必须保证s至少由MB_CUR_MAX字节
 * 如果s非NULL，返回写入到s中的字节数
 * 如果s为NULL，该函数重置内部的Shift state为初始状态，并返回
 * 零（如果多字节编码方式是无状态的）或非零
 */
int wctomb(char *s, wchar_t wc);</pre>
<p>举例：</p>
<p><pre class="crayon-plain-tag">setlocale( LC_ALL, "" );
char buf[64];
char *str = buf;
str += wctomb( str, L'你' );
str += wctomb( str, L'好' );
printf( "%s", buf ); //你好</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wprintf()</pre> </td>
<td>
<pre class="crayon-plain-tag">//下面三个函数和对应单字节字符的版本功能类似
int fwprintf( FILE *stream, const wchar_t *format, ... );
int wprintf( const wchar_t *format, ... );
int swprintf( wchar_t *s, size_t n, const wchar_t *format, ... ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">vwprintf()</pre> </td>
<td>
<pre class="crayon-plain-tag">//下面三个函数与上面类似，但是使用列表而不是变长参数
int vwprintf( const wchar_t *format, va_list arg );
int vfwprintf( FILE *stream, const wchar_t *format, va_list arg );
int vswprintf( wchar_t *s, size_t n, const wchar_t *format, va_list arg );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wscanf()</pre> </td>
<td>
<pre class="crayon-plain-tag">//下面三个函数和对应单字节字符的版本功能类似
int fwscanf( FILE *stream, const wchar_t *format, ... );
int wscanf( const wchar_t *format, ... );
int swscanf( const wchar_t *s, const wchar_t *format, ... );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswalnum()</pre> </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否字母或者数字
int iswalnum( wint_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswalpha()</pre> </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否字母
int iswalpha( wint_t wc ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswxdigit()</pre> </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否属于十六进制字符
int iswxdigit( wint_t wc ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswcntrl()</pre> </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否为控制字符
int iswcntrl( wint_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswgraph()</pre>  </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否为可见
int iswgraph( wint_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswprint()</pre> </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否为可打印字符
int iswprint( wint_t wc ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswspace()</pre> </td>
<td>
<pre class="crayon-plain-tag">//测试在当前Locale下，字符是否为空白字符
int iswspace( wint_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">iswupper()</pre> <br /><pre class="crayon-plain-tag">iswlower()</pre></td>
<td>
<pre class="crayon-plain-tag">//是否大小写判断
int iswupper( wint_t wc );
int iswlower( wint_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">towupper()</pre> <br /><pre class="crayon-plain-tag">towlower()</pre> </td>
<td>
<pre class="crayon-plain-tag">//转换为大小写
wint_t towupper( wint_t wc );
wint_t towlower( wint_t wc );</pre>
</td>
</tr>
<tr>
<td> <pre class="crayon-plain-tag">fgetwc()</pre> </td>
<td>
<pre class="crayon-plain-tag">//从文件流中读取下一个宽字符
wint_t fgetwc( FILE *stream );

//与上面类似，但是作为宏实现
wint_t getwc(FILE *stream);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">getwchar()</pre> </td>
<td>
<pre class="crayon-plain-tag">//从标准输入读取一个宽字符
wint_t getwchar(void);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">fputwc()</pre> </td>
<td>
<pre class="crayon-plain-tag">//写入一个宽字符到文件流
wint_t fputwc( wchar_t wc, FILE *stream );

//与上面类似，但是作为宏实现
wint_t putwc(wchar_t wc, FILE *stream);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">putwchar()</pre> </td>
<td>
<pre class="crayon-plain-tag">//写入一个宽字符到标准输出
wint_t putwchar( wchar_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">fgetws()</pre> </td>
<td>
<pre class="crayon-plain-tag">//从文件流中读取宽字符串
wchar_t *fgetws( wchar_t *ws, int n, FILE *stream );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">fputws()</pre> </td>
<td>
<pre class="crayon-plain-tag">//写入宽字符串到文件流
int fputws( const wchar_t *ws, FILE *stream ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">fwide()</pre>  </td>
<td>
<pre class="crayon-plain-tag">/**
 * 修改流为面向字节/面向宽字符
 * @param stream 目标流
 * @param mode 1尝试修改为面向宽字符；-1尝试修改为面向字节；0不变
 */
int fwide( FILE *stream, int mode );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcscat()</pre> </td>
<td>
<pre class="crayon-plain-tag">//连接两个宽字符串
wchar_t *wcscat( wchar_t *ws1, const wchar_t *ws2 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcsncat()</pre> </td>
<td>
<pre class="crayon-plain-tag">//连接ws2的最多n个字符到ws1，不包括\0字符
wchar_t *wcsncat( wchar_t *ws1, const wchar_t *ws2, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcschr()</pre> </td>
<td>
<pre class="crayon-plain-tag">/**
 * 搜索字符串，返回字符在串中第一次出现
 * @param ws 字符串
 * @param wc 搜索的字符
 * @return 第一次出现的字符的指针，或者NULL
 */
wchar_t *wcschr( const wchar_t *ws, wchar_t wc );

//返回字符在串中的最后一次出现
wchar_t *wcsrchr( const wchar_t *ws, wchar_t wc );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcspbrk()</pre> </td>
<td>
<pre class="crayon-plain-tag">//得到第一个出现在ws1中的任何ws2中的字符
wchar_t *wcspbrk( const wchar_t *ws1, const wchar_t *ws2 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcscmp()</pre> </td>
<td>
<pre class="crayon-plain-tag">/**
 * 比较两个宽字符串，如果ws1大于ws2，返回正数；等于则返回0；小于返回负数
 * 非零返回值说明了两者的差异
 */
int wcscmp( const wchar_t *ws1, const wchar_t *ws2 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcscpy()</pre> </td>
<td>
<pre class="crayon-plain-tag">//将字符串ws2拷贝到ws1，如果两个串存在字符重叠，则行为未定义
wchar_t *wcscpy( wchar_t *ws1, const wchar_t *ws2 );

//将字符串ws2的最多n个字符拷贝到ws1，如果出现字符重叠，则行为未定义
wchar_t *wcsncpy( wchar_t *ws1, const wchar_t *ws2, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcsftime()</pre> </td>
<td>
<pre class="crayon-plain-tag">//将日期时间转换为宽字符串
size_t wcsftime( wchar_t *wcs, size_t maxsize, const wchar_t *format, const struct tm *timptr );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcslen()</pre> </td>
<td>
<pre class="crayon-plain-tag">//得到宽字符串的长度，不包括结尾的0字符，结果是字符的个数，而不是字节数
size_t wcslen( const wchar_t *ws );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcsstr()</pre> </td>
<td>
<pre class="crayon-plain-tag">//搜索子串的第一次出现，如果找不到返回NULL，如果ws2是空串，那么直接返回ws1
wchar_t *wcsstr( const wchar_t *ws1, const wchar_t *ws2 );

//类似上面的宏版本
wchar_t *wcswcs( const wchar_t *ws1, const wchar_t *ws2 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wcstok()</pre> </td>
<td>
<pre class="crayon-plain-tag">//根据分隔符，分隔宽字符串
wchar_t *wcstok( wchar_t *ws1, const wchar_t *ws2, wchar_t **ptr );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wmemchr()</pre> </td>
<td>
<pre class="crayon-plain-tag">//在长度为n字符的ws中寻找第一次出现的wc
wchar_t *wmemchr( const wchar_t *ws, wchar_t wc, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wmemcmp()</pre> </td>
<td>
<pre class="crayon-plain-tag">//比较两个字符串的前n个字符
int wmemcmp( const wchar_t *ws1, const wchar_t *ws2, size_t n );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wmemcpy()</pre> </td>
<td>
<pre class="crayon-plain-tag">//把ws2的前n个字符拷贝到ws1，返回ws1
wchar_t *wmemcpy( wchar_t *ws1, const wchar_t *ws2, size_t n ); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">wmemset()</pre> </td>
<td>
<pre class="crayon-plain-tag">//设置ws的前n个字符为wc
wchar_t *wmemset( wchar_t *ws, wchar_t wc, size_t n ); </pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">格式化输出</span></div>
<p>stdio.h中定义了一系列用于格式化输出的函数，包括：</p>
<pre class="crayon-plain-tag">#include &lt;stdio.h&gt;
/**
 * 将format指定的格式使用后续参数填充后，打印到标准输出
 */
int printf( const char *format, ... );
/**
 * 将format指定的格式使用后续参数填充后，打印到流stream
 */
int fprintf( FILE *stream, const char *format, ... );
/**
 * 将format指定的格式使用后续参数填充后，打印到str指定的缓冲区
 */
int sprintf( char *str, const char *format, ... );
//类似上面，但是最多打印size字节
int snprintf( char *str, size_t size, const char *format, ... );

#include &lt;stdarg.h&gt;
// 类似上面，但是使用使用va_list而不是变长参数列表
int vprintf( const char *format, va_list ap );
int vfprintf( FILE *stream, const char *format, va_list ap );
int vsprintf( char *str, const char *format, va_list ap );
int vsnprintf( char *str, size_t size, const char *format, va_list ap );</pre>
<p>此外，对应的还有格式化输入的函数：</p>
<pre class="crayon-plain-tag">#include &lt;stdio.h&gt;

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

#include &lt;stdarg.h&gt;

int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);</pre>
<p>这些函数的宽字符版本在wchar.h中声明。</p>
<p>上述所有函数中的format遵守一致的规范。format由<span style="background-color: #c0c0c0;">普通字符和若干转换规则</span>（conversion specifications）组成。后者导致<span style="background-color: #c0c0c0;">列表中下一个参数</span>被转换并打印。转换规则的语法为：</p>
<pre class="crayon-plain-tag">%[#0- +'][宽度][.精度][长度限定符][转换符]</pre>
<p>其中：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">标记</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%</td>
<td>表示转换规则的开始</td>
</tr>
<tr>
<td># 0 - + '</td>
<td>
<p>这些字符紧随着%，可以出现一个或者多个：<br /><span style="background-color: #c0c0c0;"><strong>#</strong></span> 表示输出为备选格式：对于o转换符需要输出为0开头，对于x或X需要输出为0x/0X开头。对于a, A, e, E, f, F, g会总是输出小数点<br /><span style="background-color: #c0c0c0;"><strong>0</strong></span> 基于0来补白，对于d, i, o, u, x, X, a, A, e, E, f, F, g,G转换后的值在左边使用<span style="background-color: #c0c0c0;">0而不是空格</span>填充，如果指定-或者精度该标记被忽略<br /><span style="background-color: #c0c0c0;"><strong>-</strong></span>  进行左对齐，默认是右侧对齐<br /><span style="background-color: #c0c0c0;"><strong>空格</strong></span> 对于有符号的转换符，在正数前添加一个空格<br /><span style="background-color: #c0c0c0;"><strong>'</strong></span> 对于数字转换符，使用千分位分组</p>
</td>
</tr>
<tr>
<td>宽度</td>
<td>表示字段的最小宽度，如果目标参数长度不够，会自动补白</td>
</tr>
<tr>
<td>.精度</td>
<td>精度：字符串的最大的字符数；浮点数小数部分的位数；整数的最小数字个数</td>
</tr>
<tr>
<td>长度限定符</td>
<td><span style="background-color: #c0c0c0;"><strong>hh</strong></span> 如果后续整数转换符则输出signed/unsigned char，后续n转换符则输出signed char*<br /><span style="background-color: #c0c0c0;"><strong>h  </strong></span>  如果后续整数转换符则输出signed/unsigned short，后续n转换符则输出signed short*<br /><span style="background-color: #c0c0c0;"><strong>l  </strong> </span>  如果后续整数转换符则输出signed/unsigned long，后续n转换符则输出signed long*<br />      后续c转换符则输出wint_t，<span style="background-color: #c0c0c0;">后续s则输出wchar_t*</span><br /><strong><span style="background-color: #c0c0c0;">ll </span> </strong>  如果后续整数转换符则输出signed/unsigned long long，后续n转换符则输出signed long long*<br /><span style="background-color: #c0c0c0;"><strong>L </strong></span>   如果后续a, A, e, E, f, F, g或G则输出long double格式</td>
</tr>
</tbody>
</table>
<p>可用的转换符如下表：</p>
<table class="full-width fixed-word-wrap" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 15%; text-align: center;"> 转换符</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>d,i</td>
<td>int，格式化为十进制整数<br />可以附加前缀l，用于格式化long类型，例如li、ld</td>
</tr>
<tr>
<td>o</td>
<td>int，格式化为八进制整数（无符号），默认前缀0省略</td>
</tr>
<tr>
<td>x,X</td>
<td>int，格式化为十六进制整数（无符号），默认前缀0x省略。X表示字母大写打印</td>
</tr>
<tr>
<td>u</td>
<td>unsigned int，打印为无符号整数<br />可以附加前缀l，用于格式化long类型，例如lu</td>
</tr>
<tr>
<td>c</td>
<td>int，打印单个字符</td>
</tr>
<tr>
<td>s</td>
<td>char*，打印字符串，直到遇到\0，或者到达精度限制</td>
</tr>
<tr>
<td>f</td>
<td>double，打印为：[-]m.dddddd，小数部分默认6</td>
</tr>
<tr>
<td>e,E</td>
<td>double，打印为：[-]m.dddddde+/-xx或者[-]m.ddddddE+/-xx，其中d为精度</td>
</tr>
<tr>
<td>g,G</td>
<td>double，如果指数小于-4或者大于等于精度，使用%e、%E输出；否则使用%f输出，尾部的0和小数点不打印</td>
</tr>
<tr>
<td>p</td>
<td>void*，打印指针</td>
</tr>
<tr>
<td>%</td>
<td>原样打印字符%</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">第三方库</span></div>
<p>C标准库的功能非常有限，并且比较难用或存在性能问题，很多情况下需要使用第三方库。</p>
<div class="blog_h2"><span class="graybg">SDS</span></div>
<p>SDS是一个简单的动态字符串库，原本是Redis内部的组件。它在内部维护一个基于堆的缓冲区，避免受到C标准库的限制，SDS<span style="background-color: #c0c0c0;">兼容普通的C字符串函数</span>。</p>
<p>典型的C动态字符串库是基于下面的结构实现的：</p>
<pre class="crayon-plain-tag">struct DynamicString {
    char *buf;
    size_t len;
    //其它字段
};</pre>
<p>SDS没有遵循这一模式，它由<span style="background-color: #c0c0c0;">头部+二进制安全的C风格字符串+NULL</span>字符构成。SDS的结构导致它具有一些缺点和优点。</p>
<div class="blog_h3"><span class="graybg">SDS的缺点</span></div>
<p>SDS的很多函数会<span style="background-color: #c0c0c0;">可能</span>返回一个新字符串，而不是修改原有字符串，所以很多SDS API必须这样使用：</p>
<pre class="crayon-plain-tag">s = sdscat(s,"more data");</pre>
<p>如果忘记把返回值赋值给原先的变量可能导致BUG。</p>
<p>进一步讲，如果你在多个地方引用同一个SDS，调用上述函数后必须赋值所有引用。</p>
<div class="blog_h3"><span class="graybg">SDS的优势</span></div>
<p>你可以直接对SDS变量使用C标准库函数，例如：</p>
<pre class="crayon-plain-tag">printf("%s\n", sds);
//其它库一般是这样：
printf("%s\n", str-&gt;buf);
//或者这样：
printf("%s\n", getStringPointer(str));</pre>
<p>索引方式访问单个字符也是支持的：</p>
<pre class="crayon-plain-tag">printf("%c %c\n", sds[0], sds[1]);</pre>
<div class="blog_h3"><span class="graybg">SDS的API</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 22%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><pre class="crayon-plain-tag">sdsnew()</pre> <br /><pre class="crayon-plain-tag">sdsnewlen()</pre> <br /><pre class="crayon-plain-tag">sdsempty()</pre> <br /><pre class="crayon-plain-tag">sdsdup()</pre> </td>
<td>这些函数用于创建新的动态字符串：<br />
<pre class="crayon-plain-tag">/**
 * 创建一个动态字符串，初始值从init的前initlen中取得
 */
sds sdsnewlen( const void *init, size_t initlen );
/**
 * 创建一个动态字符串，初始值由init指定，init必须由\0结束
 */
sds sdsnew( const char *init );
/**
 * 创建一个空白的动态字符串
 */
sds sdsempty( void );
/**
 * 从s复制一个新的动态字符串
 */
sds sdsdup( const sds s );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdslen()</pre> </td>
<td>
<p>获得动态字符串的长度：<pre class="crayon-plain-tag">size_t sdslen(const sds s);</pre><br />类似于标准库的strlen()函数，但是：</p>
<ol>
<li>该函数消耗时间是固定的，原因是SDS的长度存放在头部字段中</li>
<li>与其它SDS函数一样，该函数也是二进制安全的。其返回的长度是字符串的真实长度，即是中间包含\0字符：<br />
<pre class="crayon-plain-tag">sdslen(sdsnewlen("A\0\0B",4)) == 4 </pre>
</li>
</ol>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsfree()</pre> </td>
<td>销毁动态字符串，即使空串也必须销毁，否则内存泄漏：<br />
<pre class="crayon-plain-tag">void sdsfree(sds s);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdscat()</pre> <br /><pre class="crayon-plain-tag">sdscatlen()</pre> <br /><pre class="crayon-plain-tag">sdscatsds()</pre> </td>
<td>连接字符串：<br />
<pre class="crayon-plain-tag">/**
 * 将缓冲区t的前len个字节连接到动态字符串中，返回（可能是）新的动态字符串
 */
sds sdscatlen( sds s, const void *t, size_t len );
/**
 * 将字符串连接到s，返回（可能是）新的动态字符串
 */
sds sdscat( sds s, const char *t );
/**
 * 将动态字符串t连接到s，返回（可能是）新的动态字符串
 */
sds sdscatsds(sds s, const sds t);</pre></p>
<p>举例： </p>
<p><pre class="crayon-plain-tag">sds hello = sdsnew( "Hello " );
sds world = sdsnew( "World" );
hello = sdscatsds( hello, world );
sdsfree( world );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsgrowzero()</pre> </td>
<td>确保动态字符串的长度：<br />
<pre class="crayon-plain-tag">// 如果s的长度大于等于len什么都不做，否则扩充到len长并使用0填充
sds sdsgrowzero(sds s, size_t len);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdscatprintf()</pre> </td>
<td>格式化字符串并连接到动态字符串：<br />
<pre class="crayon-plain-tag">/**
 * 根据fmt和后续参数进行字符串格式化，然后连接到动态字符串s中
 */
sds sdscatprintf(sds s, const char *fmt, ...);</pre></p>
<p>举例：<pre class="crayon-plain-tag">sdscatprintf(sdsempty(), "%s %s", "Hello", "Alex");</pre> </p>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsfromlonglong()</pre> </td>
<td>从数字创建字符串：<br />
<pre class="crayon-plain-tag">sds sdsfromlonglong(long long value);</pre></p>
<p>举例：</p>
<p><pre class="crayon-plain-tag">sds num = sdsfromlonglong( 9460500000000 );
assert( 0 == strcmp( "9460500000000", num ));</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdstrim()</pre> </td>
<td>修剪字符串：<br />
<pre class="crayon-plain-tag">/**
 * 修剪动态字符串s，清除左侧或者右侧的、存在于cset中的字符
 */
void sdstrim( sds s, const char *cset );</pre></p>
<p>举例：</p>
<p><pre class="crayon-plain-tag">sds str = sdsnew( "\n\nHello World   " );
sdstrim( str, "\n " );
assert( strcmp( "Hello World", str ) == 0 );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsrange()</pre> </td>
<td>修剪为子串：<br />
<pre class="crayon-plain-tag">/**
 * 修改动态字符串s，保留从start到end的部分，end包含在内
 */
void sdsrange( sds s, int start, int end );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdscpy()</pre> <br /><pre class="crayon-plain-tag">sdscpylen()</pre> </td>
<td><pre class="crayon-plain-tag">strcpy()</pre> 是C标准库中最危险和恶名的操作之一。SDS提供的相应的操作则可用于性能关键的领域：<br />
<pre class="crayon-plain-tag">/**
 * 拷贝t到动态字符串s中
 */
sds sdscpy( sds s, const char *t );
/**
 * 拷贝t的前len个字节到动态字符串s中
 */
sds sdscpylen(sds s, const char *t, size_t len);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">scscatrepr()</pre> </td>
<td>连接字符串，并将其中的不可打印字符使用转义字符的形式显示出来：<br />
<pre class="crayon-plain-tag">sds sdscatrepr(sds s, const char *p, size_t len);

//示例：
sds s = sdsempty();
char q[10];
q[0] = 'A';q[1] = 1;q[2] = 20;q[4] = '\t';q[3] = '\n';
s = sdscatrepr(s,q,10);
printf( s ); //输出："A\x01\x14\n\t\x7f\x00\x00\x00\x00"</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdssplitlen()</pre> <br /><pre class="crayon-plain-tag">sdsfreesplitres()</pre> </td>
<td>标记化（Tokenization）：将一个大字符串分隔（split）为多个小字符串：<br />
<pre class="crayon-plain-tag">/**
 * s的前len个字节用来被分割，sep的前len字节用作分隔符，count用于返回子串的数量
 */
sds *sdssplitlen( const char *s, int len, const char *sep, int seplen, int *count );
// 销毁子串资源
void sdsfreesplitres( sds *tokens, int count );</pre></p>
<p>举例：</p>
<p><pre class="crayon-plain-tag">char *str = "1986,.1989.,2014";
int count;
sds *substrs = sdssplitlen( str, strlen( str ), ",.", 2, &amp;count );
for ( int i = 0; i &lt; count; ++i ) {
    sds substr = *( substrs + i );
    printf( "%s\n", substr );
    //打印：
    //1986
    //1989.,2014
}
sdsfreesplitres( substrs, count );</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsjoin()</pre> <br /><pre class="crayon-plain-tag">sdsjoinsds()</pre> </td>
<td>使用分隔符连接一组字符串：<br />
<pre class="crayon-plain-tag">/**
 * 使用sep中的前seplen个字符作为分隔符，连接长度为argc的字符串数组
 */
sds sdsjoin( char **argv, int argc, char *sep, size_t seplen );
//类似上面
sds sdsjoinsds( sds *argv, int argc, const char *sep, size_t seplen );</pre></p>
<p> 举例：</p>
<p><pre class="crayon-plain-tag">char *strs[3] = { "foo", "bar", "zap" };
printf( sdsjoin( strs, 3, "-" )); //打印：foo-bar-zap</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsRemoveFreeSpace()</pre></td>
<td>收缩可变字符串，移除空闲的空间，在内存受限环境下可以使用：<br />
<pre class="crayon-plain-tag">sds sdsRemoveFreeSpace(sds s);</pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsAllocSize()</pre></td>
<td>获得一个可变字符串实际分配的空间：<br />
<pre class="crayon-plain-tag">size_t sdsAllocSize(sds s); </pre>
</td>
</tr>
<tr>
<td><pre class="crayon-plain-tag">sdsupdatelen()</pre></td>
<td>改变逻辑长度，反映出C字符串的长度：<br />
<pre class="crayon-plain-tag">void sdsupdatelen( sds s );

// 举例
sds s = sdsnew( "foobar" );
s[2] = '\0';
assert( sdslen( s ) == 6 );
sdsupdatelen(s);
// 更新逻辑长度后，sds的长度与C字符串长度一致
assert( sdslen( s ) == 2 );</pre>
</td>
</tr>
</tbody>
</table>
<p>上面这些函数，但凡返回sds的，在<span style="background-color: #c0c0c0;">内存溢出的情况下一律会返回空指针</span>。 </p>
<div class="blog_h3"><span class="graybg">SDS的技术细节</span></div>
<p>SDS头部由下面的数据结构表示：</p>
<pre class="crayon-plain-tag">struct sdshdr {
    int len;    // 保存动态字符串的长度
    int free;   // 保存缓冲区空闲字节数，这些空闲字节可以容纳更多的字符
    char buf[]; // 未声明长度的数组，因此它实际上指向free后面的那个字节，这很重要
};</pre>
<p>结构中的buf字段是一个<span style="background-color: #c0c0c0;">flexible array member</span>，它是<span style="background-color: #c0c0c0;">C99</span>引入的特性——位于结构体最后的无长度的数组。该数组的指针<span style="background-color: #c0c0c0;">指向紧跟着结构体的内存</span>，下面的示例代码可以验证这一点：</p>
<pre class="crayon-plain-tag">typedef struct {
    int len;
    char buf[];
} head;

int main() {
    void *mem = malloc( 1024 );
    head *h = mem;
    char *str = mem + sizeof( head );
    str[0] = 'A'; str[1] = 'B';
    printf( h-&gt;buf ); // 打印AB
    return 0;
}</pre>
<p>要创建一个SDS，只需要在堆上分配<span style="background-color: #c0c0c0;">不小于sdshdr长度+字符串长度</span>的内存即可。但是为了避免每次操控都导致新的内存分配，SDS总是<span style="background-color: #c0c0c0;">预分配一些额外的内存</span>。SDS的预分配算法是：当每次进行内存分配时，实际分配的内存是<span style="background-color: #c0c0c0;">最小需求量的2倍</span>。内存分配的最大量由宏<pre class="crayon-plain-tag">SDS_MAX_PREALLOC</pre> 控制。</p>
<div class="blog_h3"><span class="graybg">共享SDS</span></div>
<p>如果你需要在多个数据结构中共享SDS动态字符串，应当将SDS封装在<span style="background-color: #c0c0c0;">具有引用计数的结构体</span>中，避免编程错误导致的内存泄漏： </p>
<pre class="crayon-plain-tag">struct ds {
    int refcount;
    sds str;
}</pre>
<p>你应当提供增加、减少引用计数的函数：</p>
<ol>
<li>每当其它数据结构引用ds或者将ds赋值给变量时，都应当增加引用计数</li>
<li>每当引用移除时，减少计数，计数为0时，自动<span style="background-color: #c0c0c0;">销毁</span>SDS字符串</li>
</ol>
<div class="blog_h3"><span class="graybg">零拷贝（Zero copy）连接</span></div>
<p>在Redis中，为了增强性能使用了SDS提供的一些低级API。使用<pre class="crayon-plain-tag">sdsIncrLen()</pre> 和<pre class="crayon-plain-tag">sdsMakeRoomFor()</pre> 可以将来自内核的字节直接连接到SDS尾部，而不需要中介的缓冲区：</p>
<pre class="crayon-plain-tag">oldlen = sdslen(s);
s = sdsMakeRoomFor(s, BUFFER_SIZE);
nread = read(fd, s+oldlen, BUFFER_SIZE); // 系统调用
sdsIncrLen(s, nread);  </pre>
<p>  </p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/text-processing-with-c">使用C语言进行文本处理</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/text-processing-with-c/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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-69de22b3776ec773888179/] 使用flock命令 [crayon-69de22b3776f3490195263/] 色彩控制 我们可以对终端输出的字符颜色、背景色等参数进行设置，实现方式是转义序列。这个控制实际上与Bash语言无关，其它语言在输出时也可以使用。 转义序列以ESC字符开头，对应八进制的\033。控制代码格式为： [crayon-69de22b3776f5250552248/] 注意：显示方式、前景色、背景色都是可选的，而且顺序可以颠倒。示例： [crayon-69de22b3776f8827983838/] 在脚本中使用色彩控制的例子： [crayon-69de22b3776fa670275935/] 编码列表 编码 动作 &#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>
