INNODB_BUFFER_POOL_SIZE:设置最佳内存值

什么是INNODB BUFFER POOL

计算机使用它们的大部分内存来提升对经常访问的数据的性能。这就是我们所知的缓存,是系统的一个非常重要的组成部分,因为访问硬盘的数据可能会慢到100到100000倍,这取决你访问的数据量。
MyISAM是使用操作系统的文件系统缓存来缓存那些经常被查询的数据。然而InnoDB使用的是一种非常不同的方法。
不依赖操作系统的缓存,InnoDB自己在InnoDB Buffer Pool处理缓存。经过这篇文章你会学到它是如何工作的,为什么以那种方式来实施是一个不错的想法。

InnoDB缓冲池不仅仅是一个缓存

InnoDB缓冲池实际上用于多个目的,它用来:
* 数据缓存 – 这绝对是它的最重要的目的
* 索引缓存 – 这使用是的同一个缓冲池
* 缓冲 – 更改的数据(通常称为脏数据)在被刷新到硬盘之前先存放到缓冲
* 存储内部结构 – 一些结构如自适应哈希索引或者行锁也都存储在InnoDB缓冲池
下面是一个经典的把innodb-buffer-pool-size设置为62G的InnoDB缓冲池页的分布情况:
MySQL
正如你所看到的,Buffer Pool大多是用于普通的InnoDB页面,但大约10%用作其它目的。
这张表的单位是InnoDB页。单个页面大小实际上是16K,所以你可以乘以16,384来得到以字节为单位更直观的使用情况。

InnoDB缓冲池的大小

那么innodb-buffer-pool-size的大小应该设置为什么呢?下面我们就开始谈到这个。

独立服务器

在一个独立的只使用InnoDB引擎的MySQL服务器中,根据经验,推荐设置innodb-buffer-pool-size为服务器总可用内存的80%。
为什么不是90%或者100%呢?
因为其它的东西也需要内存:
* 每个查询至少需要几K的内存(有时候是几M)
* 有各种其它内部的MySQL结构和缓存
* InnoDB有一些结构是不用缓冲池的内存的(字典缓存,文件系统,锁系统和页哈希表等)
* 也有一些MySQL文件是在OS缓存里的(binary日志,relay日志,innodb事务日志等)
* 此处,你也必须为操作系统留出些内存

共享服务器

如果你的MySQL服务器与其它应用共享资源,那么上面80%的经验就不那么适用了。
在这样的环境下,设置一个对的数字有点难度。
首先让我们来统计一下InnoDB表的实际占用大小。执行如下查询:

  1. SELECT engine,
  2.   count(*) as TABLES,
  3.   concat(round(sum(table_rows)/1000000,2),’M’) rows,
  4.   concat(round(sum(data_length)/(1024*1024*1024),2),’G’) DATA,
  5.   concat(round(sum(index_length)/(1024*1024*1024),2),’G’) idx,
  6.   concat(round(sum(data_length+index_length)/(1024*1024*1024),2),’G’) total_size,
  7.   round(sum(index_length)/sum(data_length),2) idxfrac
  8. FROM information_schema.TABLES
  9. WHERE table_schema not in (‘mysql’, ‘performance_schema’, ‘information_schema’)
  10. GROUP BY engine
  11. ORDER BY sum(data_length+index_length) DESC LIMIT 10;

这会给出一个参考,让你知道如果你想缓存整个数据集应该为InnoDB缓冲池设置多少内存合适。
不过大多数情况你不需要那样做,你只需要缓存你经常使用的数据集。
设置好之后,我们来看看如何检查InnoDB缓冲池大小是否设置足够。
在终端中,执行如下命令:

  1. $ mysqladmin ext -ri1 | grep Innodb_buffer_pool_reads
  2. | Innodb_buffer_pool_reads                 | 1832098003     |
  3. | Innodb_buffer_pool_reads                 | 595            |
  4. | Innodb_buffer_pool_reads                 | 915            |
  5. | Innodb_buffer_pool_reads                 | 734            |
  6. | Innodb_buffer_pool_reads                 | 622            |
  7. | Innodb_buffer_pool_reads                 | 710            |
  8. | Innodb_buffer_pool_reads                 | 664            |
  9. | Innodb_buffer_pool_reads                 | 987            |
  10. | Innodb_buffer_pool_reads                 | 1287           |
  11. | Innodb_buffer_pool_reads                 | 967            |
  12. | Innodb_buffer_pool_reads                 | 1181           |
  13. | Innodb_buffer_pool_reads                 | 949            |

你所看到的是从硬盘读取数据到缓冲池的次数(每秒)。上面的数据已经相当高了(幸运的是,这个服务器的IO设备能处理每秒4000的IO操作),如果这个是OLTP系统,我建议提高innodb缓冲池的大小和如果必要增加服务器内存。

更改InnoDB缓冲池

最后,介绍如何更改innodb-buffer-pool-size。
如果你运行的是MySQL 5.7,那么非常幸运,你可以在线更改这个变量,只需要以root身份执行如下查询:

  1. mysql> SET GLOBAL innodb_buffer_pool_size=size_in_bytes;

这还没完,你仍然需要更改my.cnf文件,不过至少你不需要重启服务器让它生效。从mysql的错误日志中我们可以看到它生效的过程:

  1. [Note] InnoDB: Resizing buffer pool from 134217728 to 21474836480. (unit=134217728)
  2. [Note] InnoDB: disabled adaptive hash index.
  3. [Note] InnoDB: buffer pool 0 : 159 chunks (1302369 blocks) were added.
  4. [Note] InnoDB: buffer pool 0 : hash tables were resized.
  5. [Note] InnoDB: Resized hash tables at lock_sys, adaptive hash index, dictionary.
  6. [Note] InnoDB: Completed to resize buffer pool from 134217728 to 21474836480.
  7. [Note] InnoDB: Re-enabled adaptive hash index.

在更早的mysql版本就需要重启了,所以:
1. 在my.cnf中设置一个innodb_buffer_pool_size合适的值
2.重启mysql服务器

DEFAULT_STORAGE_ENGINE:选择正确的MySQL存储引擎

现在让我们把MySQL存储引擎的问题放在一边。如果你的MySQL表都是用的InnoDB而你不需要关心InnoDB是如何运作的,你已经设置了,但不确定是否生效。这些问题将在下面会提到。

关于存储引擎

MySQL自20多年前成立以来一直支持可插拔存储引擎,但在一段相当长的时间里MyISAM一直是默认的存储引擎,许多人运行MySQL甚至对底层存储引擎一点都不了解。毕竟,MySQL刚开始是为小型网站的小型数据库设计的,许多应用已经习惯使用MyISAM存储引擎。

刚开始没什么问题,一切正常,但现在的问题是:MyISAM没有考虑到应用到高并发高负载,多核CPU和RAID阵列的场景,也不能弹性扩展。所以网站流量越来越多后,他们不能扩展,因为MySQL查询会在表级锁上等待数秒(MyISAM只支持这种锁机制)。他们不想每次MySQL崩溃时损坏他们的业务数据。

INNODB存储引擎

许多人并不知道,自MySQL存在以来MyISAM存储引擎就有一个兄弟叫InnoDB。并且高并发负载,性能和弹性(也包括原子性,一致性和隔离)正是它的特长。
当然,在InnoDB发展过程中也有过一些问题(尤其是2006年5.0.30之前的版本的性能问题),但在这之后的10年时间里,InnoDB已经在你能想到的领域(或者没有)得到了证明,而MyISAM已经很少被关注了。
因此,从MySQL 5.5.5开始,InnoDB成为默认的存储引擎,现在你几乎找不到大型MySQL数据库的安装使用MyISAM而不是InnoDB。
下面让我来告诉你如何快速地统计和列出在你系统的所有MyISAM表,方便你开始计划迁移。

你使用的存储引擎

下面的查询展示你所用的存储引擎以及它们的一些统计信息,包括表数量,大小等。

  1. mysql> SELECT engine,
  2.   count(*) as TABLES,
  3.   concat(round(sum(table_rows)/1000000,2),’M’) rows,
  4.   concat(round(sum(data_length)/(1024*1024*1024),2),’G’) DATA,
  5.   concat(round(sum(index_length)/(1024*1024*1024),2),’G’) idx,
  6.   concat(round(sum(data_length+index_length)/(1024*1024*1024),2),’G’) total_size,
  7.   round(sum(index_length)/sum(data_length),2) idxfrac
  8.  FROM information_schema.TABLES
  9. WHERE table_schema not in (‘mysql’, ‘performance_schema’, ‘information_schema’)
  10. GROUP BY engine
  11. ORDER BY sum(data_length+index_length) DESC LIMIT 10;
  12. +——–+——–+———+——–+——–+————+———+
  13. | engine | TABLES | rows    | DATA   | idx    | total_size | idxfrac |
  14. +——–+——–+———+——–+——–+————+———+
  15. | InnoDB |    181 | 457.58M | 92.34G | 54.58G | 146.92G    |    0.59 |
  16. | MyISAM |     13 | 22.91M  | 7.85G  | 2.12G  | 9.97G      |    0.27 |
  17. +——–+——–+———+——–+——–+————+———+
  18. 2 rows in set (0.22 sec)

获取以大小排序的MyISAM表列表,执行如下查询:

  1. SELECT
  2.     concat(table_schema, ‘.’, table_name) tbl,
  3.     engine,
  4.     concat(round(table_rows/1000000,2),’M’) rows,
  5.     concat(round(data_length/(1024*1024*1024),2),’G’) DATA,
  6.     concat(round(index_length/(1024*1024*1024),2),’G’) idx,
  7.     concat(round((data_length+index_length)/(1024*1024*1024),2),’G’) total_size,
  8.     round(index_length/data_length,2) idxfrac
  9.  FROM information_schema.TABLES
  10. WHERE table_schema not in (‘mysql’, ‘performance_schema’, ‘information_schema’)
  11.   AND engine = ‘MyISAM’
  12. ORDER BY data_length+index_length DESC;

需要记住的是,更改默认的存储引擎为InnoDB或者升级MySQL并不会自动把你的表转换为InnoDB。目前为止,你需要一个表一个表地转换,或者使用脚本。
需要注意的是,小的MyISAM表也一样需要转换,因为只要有一个MyISAM用在join语句里,那么整个查询都是用表级锁,所以这将对并发有很大影响。所以确保你把所有的MyISAM表转为InnoDB表。

转换为INNODB

建议在你着手转换引擎为InnoDB之前,最好先熟悉理解一下InnoDB的配置。准备好后,执行如下查询来转换:

  1. SET @DB_NAME = ‘your_database’;
  2.  
  3. SELECT  CONCAT(‘ALTER TABLE `’, table_name, ‘` ENGINE=InnoDB;’) AS sql_statements
  4. FROM    information_schema.tables AS tb
  5. WHERE   table_schema = @DB_NAME
  6. AND     `ENGINE` = ‘MyISAM’
  7. AND     `TABLE_TYPE` = ‘BASE TABLE’
  8. ORDER BY table_name DESC;

Bash使用示例(5) – 条件表达式

文件类型测试

-e条件运算符用来测试一个文件是否存在(包括所有文件类型,目录等)

  1. if [[ -e $filename ]]; then
  2.   echo "$filename exists"
  3. fi

也可以测试指定类型的文件

  1. if [[ -f $filename ]]; then
  2.   echo "$filename is a regular file"
  3. elif [[ -d $filename ]]; then
  4.   echo "$filename is a directory"
  5. elif [[ -p $filename ]]; then
  6.   echo "$filename is a named pipe"
  7. elif [[ -S $filename ]]; then
  8.   echo "$filename is a named socket"
  9. elif [[ -b $filename ]]; then
  10.   echo "$filename is a block device"
  11. elif [[ -c $filename ]]; then
  12.   echo "$filename is a character device"
  13. fi
  14. if [[ -L $filename ]]; then
  15.   echo "$filename is a symbolic link (to any file type)"
  16. fi

对于软链接,使用-L测试时,当链接指向的目标文件不存在时会返回false

  1. if [[ -L $filename || -e $filename ]]; then
  2.   echo "$filename exists (but may be a broken symbolic link)"
  3. fi
  4.  
  5. if [[ -L $filename && ! -e $filename ]]; then
  6.   echo "$filename is a broken symbolic link"
  7. fi

字符串比较和匹配

在两个带引号的字符串之间比较时使用==操作符。!=操作符则是否则的比较

  1. if [[ "$string1" == "$string2" ]]; then
  2.   echo "$string1 and $string2 are identical"
  3. fi
  4. if [[ "$string1" == "$string2" ]]; then
  5.   echo "$string1 and $string2 are identical"
  6. fi

如果右则没有引号,那么它是以通配符的模式作比较。

  1. string1=’abc’
  2. pattern=’a*’
  3. if [[ "$string1" == $pattern ]]; then
  4.   # the test is true
  5.   echo "The string $string1 matches the pattern $pattern"
  6. fi
  7. if [[ "$string1" != "$pattern" ]]; then
  8.   echo "The string $string1 is not equal to the string $pattern"
  9. fi

操作符以字典顺序来比较字符串(它们没有小或等于或大于的说法)
可以测试空字符串

  1. if [[ -n $string ]]; then
  2.   echo "$string is non-empty"
  3. fi
  4. if [[ -z "${string// }" ]]; then
  5.   echo "$string is empty or contains only spaces"
  6. fi
  7. if [[ -z $string ]]; then
  8.   echo "$string is empty"
  9. fi

以上的-z检查可能表示$string没有被设置或者已经设置了但为空字符。为了区分空字符和未设置,使用:

  1. if [[ -n ${string+x} ]]; then
  2.     echo "$string is set, possibly to the empty string"
  3. fi
  4. if [[ -n ${string-x} ]]; then
  5.     echo "$string is either unset or set to a non-empty string"
  6. fi
  7. if [[ -z ${string+x} ]]; then
  8.     echo "$string is unset"
  9. fi
  10. if [[ -z ${string-x} ]]; then
  11.     echo "$string is set to an empty string"
  12. fi

其中x是任意的,以表格形式如下:

  1. +——-+——-+———–+
  2.             $string is: | unset | empty | non-empty |
  3. +———————–+——-+——-+———–+
  4. | [[ -z ${string} ]]    | true  | true  | false     |
  5. | [[ -z ${string+x} ]]  | true  | false | false     |
  6. | [[ -z ${string-x} ]]  | false | true  | false     |
  7. | [[ -n ${string} ]]    | false | false | true      |
  8. | [[ -n ${string+x} ]]  | false | true  | true      |
  9. | [[ -n ${string-x} ]]  | true  | false | true      |
  10. +———————–+——-+——-+———–+

文件权限测试

  1. if [[ -r $filename ]]; then
  2.   echo "$filename is a readable file"
  3. fi
  4. if [[ -w $filename ]]; then
  5.   echo "$filename is a writable file"
  6. fi
  7. if [[ -x $filename ]]; then
  8.   echo "$filename is an executable file"
  9. fi

Bash使用示例(4) – 重定向输出

重定向标准输出

> 重定向当前命令的标准输出(STDOUT)到一个文件或者一个描述符。
下面的例子把ls命令的输出存到file.txt文件

  1. ls >file.txt
  2. > file.txt ls

目标文件如果不存在就会被创建,或者文件被截断。
如果不指定,默认重定向描述符是标准输出或1。下面的命令等同于上面的例子:

  1. ls 1>file.txt

追加 vs 截断

截断 >
1.如果文件不存在则创建
2.截断(删除文件内容)
3.写入文件

  1. $ echo "first line" > /tmp/lines
  2. $ echo "second line" > /tmp/lines
  3.  
  4. $ cat /tmp/lines
  5. second line

追加 >>
1.如果文件不存在则创建
2.追加文件(在文件底部写)

  1. # Overwrite existing file
  2. $ echo "first line" > /tmp/lines
  3.  
  4. # Append a second line
  5. $ echo "second line" >> /tmp/lines
  6.  
  7. $ cat /tmp/lines
  8. first line
  9. second line

重定向标准输出和标准错误

文件描述符像0和1都是指针。我们更改的是文件描述符的指向。
>/dev/null意思是1指向/dev/null。
首先我们把1(STDOUT)指向/dev/null,然后2指向1(不管1指向什么)。

  1. echo_to_stdout_and_stderr >/dev/null 2>&1

可以更短点:
echo_to_stdout_and_stderr &> /dev/null

使用命名管道

有时候你想把一个程序的标准输出作为其它多个程序的标准输入,这时候就不能用标准管道了,不过你可以写入一个临时文件,如:

  1. touch tempFile.txt
  2. ls -l > tempFile.txt &
  3. grep ".log" < tempFile.txt

这个方法可以在大多数情况下有效,但谁都不知道tempFile.txt会被哪个程序删除或者修改里面的内容。这时候命名管道就可以用上场了。

  1. mkfifo myPipe
  2. ls -l > myPipe
  3. grep ".log" < myPipe

myPipe在技术上是一个文件,所以我们来用ls -l看下当前创建管道的目录

  1. mkdir pipeFolder
  2. cd pipeFolder
  3. mkfifo myPipe
  4. ls -l

输出为:
prw-r–r– 1 root root 0 Jul 25 11:20 myPipe
注意权限的第一个字符,显示是pipe,不是文件。
现在我们做了有意思的。
打开一个终端,在一个空目录创建管道:

  1. mkfifo myPipe

现在我们输入点东西到管道:

  1. echo "Hello from the other side" > myPipe

你会注意到这个命令被挂起了,让我们打开一个新的终端,输入:

  1. cat < myPipe

你会发现当”hello from the other side”输出后,终端1就完成了,终端2也一样。
现在我们反向运行程序,先执行cat < myPipe,然后再输入点东西到myPipe,它仍然按预期工作,因为一个程序会一起等待直到管道中被输入一些东西。
命名管道在终端间或程序间传递信息时会非常有用。

输出错误信息到标准错误

错误信息通常为了调度会包含在脚本里。简单的输出错误信息如下:

  1. cmd || echo ‘cmd failed’

可能会在简单的场景工作,但不是通常的做法。在这个例子中,错误信息会会污染脚本实际的输出。简单来说,错误信息应该输出到标准错误而不是标准输出,如:

  1. cmd || echo ‘cmd failed’ >/dev/stderr

其它例子:

  1. if cmd; then
  2.     echo ‘success’
  3. else
  4.     echo ‘cmd failed’ >/dev/stderr
  5. fi

可以封装成一个函数:

  1. err(){
  2.     echo "E: $*" >>/dev/stderr
  3. }
  4. err "My error message"

Bash使用示例(3) – 使用trap处理信号

清理临时文件

你可以使用trap命令来捕获信号;shell中的trap捕获信号等同于C语言或大多数其它语言中的signal或者sigaction。
trap最常用的场景之一是在预期退出和意外退出时清理临时文件。
遗憾的是没有多少shell脚本这样做。

  1. #!/bin/sh
  2.  
  3. # Make a cleanup function
  4. cleanup() {
  5.   rm –force — "${tmp}"
  6. }
  7.  
  8. # Trap the special "EXIT" group, which is always run when the shell exits.
  9. trap cleanup EXIT
  10.  
  11. # Create a temporary file
  12. tmp="$(mktemp -p /tmp tmpfileXXXXXXX)"
  13.  
  14. echo "Hello, world!" >> "${tmp}"
  15.  
  16. # No rm -f "$tmp" needed. The advantage of using EXIT is that it still works
  17. # even if there was an error or if you used exit.

捕获SIGINT或Ctrl+C信号

当有子shell时,trap会被重置,所以sleep继续作用于由^C发送的SIGINT信号,而父进程(即shell脚本)就不会了。所以下面的例子只是退出了sleep,没有直接退出shell脚本,而是继续往下执行。

  1. #!/bin/sh
  2.  
  3. # Run a command on signal 2 (SIGINT, which is what ^C sends)
  4. sigint() {
  5.     echo "Killed subshell!"
  6. }
  7. trap sigint INT
  8.  
  9. # Or use the no-op command for no output
  10. #trap : INT
  11.  
  12. # This will be killed on the first ^C
  13. echo "Sleeping…"
  14. sleep 500
  15.  
  16. echo "Sleeping…"
  17. sleep 500

通过些许更改可以允许你按两个^C才退出程序:

  1. last=0
  2. allow_quit() {
  3.     [ $(date +%s) -lt $(( $last + 1 )) ] && exit
  4.     echo "Press ^C twice in a row to quit"
  5.     last=$(date +%s)
  6. }
  7. trap allow_quit INT

统计维护退出任务

你有没有在退出时忘记添加trap来清理临时文件或者其它事情?
有没有设置了一个trap而导致取消了另一个?
下面的代码能让你非常容易地在一个地方就把所有需要在退出时做的工作都添加进去,而不是需要一大段的trap声明,这个很容易忘记的。

  1. # on_exit and add_on_exit
  2. # Usage:
  3. #   add_on_exit rm -f /tmp/foo
  4. #   add_on_exit echo "I am exiting"
  5. #   tempfile=$(mktemp)
  6. #   add_on_exit rm -f "$tempfile"
  7. # Based on http://www.linuxjournal.com/content/use-bash-trap-statement-cleanup-temporary-files
  8. function on_exit()
  9. {
  10.     for i in "${on_exit_items[@]}"
  11.     do
  12.         eval $i
  13.     done
  14. }
  15. function add_on_exit()
  16. {
  17.     local n=${#on_exit_items[*]}
  18.     on_exit_items[$n]="$*"
  19.     if [[ $n -eq 0 ]]; then
  20.         trap on_exit EXIT
  21.     fi
  22. }

退出时杀掉子进程

Trap表达式不一定需要单独的函数或者程序,它也可以直接使用复杂的表达式,如:

  1. trap ‘jobs -p | xargs kill’ EXIT

Bash使用示例(2) – 内部变量

$@

“$@”把所有的命令行参数作为一个数组返回。与”$*”不一样,它是作为一个字符串来返回。
“$@”可以通过循环来遍历所有元素,如下脚本:

  1. #!/bin/bash
  2. for var in "$*"; do
  3.     echo $var
  4. done

因为$*只把参数作为一个字符串返回,echo就只被调用一次:

  1. ~> $ ./testscript.sh firstarg secondarg thirdarg
  2. firstarg secondarg thirdarg

使用$@时:

  1. #!/bin/bash
  2. for var in "$@"; do
  3.     echo $var
  4. done

以数组返回所有参数,使用你能够单独地访问每一个参数:

  1. ~> $ ./testscript.sh firstarg secondarg thirdarg
  2. firstarg
  3. secondarg
  4. thirdarg

$#

获取命令行参数个数,键入:

  1. #!/bin/bash
  2. echo "$#"

如带3个参数运行脚本,输出如下:

  1. ~> $ ./testscript.sh firstarg secondarg thirdarg
  2. 3

$!

返回上一个程序执行的进程ID:

  1. ~> $ ls &
  2. testfile1 testfile2
  3. [1]+  Done                    ls
  4. ~> $ echo $!
  5. 21715

$$

当前进程的pid,如果在bash命令行下执行,相当于是bash进程的pid:

  1. ~> $ echo $$
  2. 13246

$*

以单个字符串返回所有命令行参数。
testscript.sh:

  1. #!/bin/bash
  2. echo "$*"

带几个参数运行脚本:

  1. ./testscript.sh firstarg secondarg thirdarg

输出:

  1. firstarg secondarg thirdarg

$?

返回上一次函数或命令执行的退出状态。通过0表示执行成功,其它的则表示执行失败:

  1. ~> $ ls *.blah;echo $?
  2. ls: cannot access *.blah: No such file or directory
  3. 2
  4. ~> $ ls;echo $?
  5. testfile1 testfile2
  6. 0

$1 $2 $3等

从命令行传递给脚本或者一个函数的位置参数:

  1. #!/bin/bash
  2. # $n is the n’th positional parameter
  3. echo "$1"
  4. echo "$2"
  5. echo "$3"

输出如下:
~> $ ./testscript.sh firstarg secondarg thirdarg

  1. firstarg
  2. secondarg
  3. thirdarg

如果位置参数的数量大于9,需要使用大括号:

  1. #  "set — " sets positional parameters
  2. set — 1 2 3 4 5 6 7 8 nine ten eleven twelfe
  3. echo $10   # outputs 1
  4. echo ${10} # outputs ten

$FUNCNAME

获取当前函数的名称:

  1. my_function()
  2. {
  3.     echo "This function is $FUNCNAME"    # This will output "This function is my_function"
  4. }

如果在函数外打印此变量:

  1. my_function
  2.  
  3. echo "This function is $FUNCNAME"    # This will output "This function is"

$HOME

用户的主目录

  1. ~> $ echo $HOME
  2. /home/user

$IFS

此变量包含用于循环中bash拆分字符串的内部字段分隔符。默认是空白字符n(newline),t(tab)或者空格。更改它的话使你能够使用不同分隔符拆分字符串:

  1. IFS=","
  2. INPUTSTR="a,b,c,d"
  3. for field in ${INPUTSTR}; do
  4.     echo $field
  5. done

输出:
a
b
c
d

$PWD

输出当前工作目录

  1. ~> $ echo $PWD
  2. /home/user
  3. ~> $ cd directory
  4. directory> $ echo $PWD
  5. /home/user/directory

$HOSTNAME

系统启动时分配的主机名

  1. ~> $ echo $HOSTNAME
  2. mybox.mydomain.com

$LINENO

输出脚本的当前行号。在调试脚本时可能会用到。

  1. #!/bin/bash
  2. # this is line 2
  3. echo something  # this is line 3
  4. echo $LINENO # Will output 4

Bash使用示例(1) – 数组

数组赋值

列表赋值

用新元素创建数组

  1. array=(‘first element’ ‘second element’ ‘third element’)

下标赋值

显式指定元素索引创建数组:

  1. array=([3]=’fourth element’ [4]=’fifth element’)

按索引赋值

  1. array[0]=’first element’
  2. array[1]=’second element’

按名称赋值(关联数组)

  1. declare -A array
  2. array[first]=’First element’
  3. array[second]=’Second element’

动态赋值

以其它命令的输出创建一个数组,例如使用seq创建一个从1到10的数组:

  1. array=(`seq 1 10`)

从脚本参数创建数组

  1. array=("$@")

循环内赋值

  1. while read -r; do
  2.     #array+=("$REPLY")     # Array append
  3.     array[$i]="$REPLY"     # Assignment by index
  4.     let i++                # Increment index
  5. done < <(seq 1 10)  # command substitution
  6. echo ${array[@]}    # output: 1 2 3 4 5 6 7 8 9 10

访问数组元素

打印索引为0的元素

  1. echo "${array[0]}"

打印最后一个元素(从Bash 4.3可用)

  1. echo "${array[-1]}"

打印从索引1开始的元素

  1. echo "${array[@]:1}"

打印从索引1开始的3个元素

  1. echo "${array[@]:1:3}"

数组更改

按索引更改

初始化或者更新数组中的一个特定元素

  1. array[10]="elevenths element"    # because it’s starting with 0

追回

修改数组,追加元素到数组结尾

  1. array+=(‘fourth element’ ‘fifth element’)

添加元素到数组开头

  1. array=("new element" "${array[@]}")

插入

给定索引值插入一个元素

  1. arr=(a b c d)
  2. # insert an element at index 2
  3. i=2
  4. arr=("${arr[@]:0:$i}" ‘new’ "${arr[@]:$i}")
  5. echo "${arr[2]}" #output: new

删除

使用uset删除指定索引元素

  1. arr=(a b c)
  2. echo "${arr[@]}"   # outputs: a b c
  3. echo "${!arr[@]}"  # outputs: 0 1 2
  4. unset -v ‘arr[1]’
  5. echo "${arr[@]}"   # outputs: a c
  6. echo "${!arr[@]}"  # outputs: 0 2

重排索引

当有一些元素从数组被删除时,可以使用下面方法重排索引,或者你不知道索引是否存在时隙时会有用。

  1. array=("${array[@]}")

数组长度

${#array[@]}可以得到${array[@]}数组的长度

  1. array=(‘first element’ ‘second element’ ‘third element’)
  2. echo "${#array[@]}" # gives out a length of 3

迭代数组元素

  1. fileList=( file1.txt file2.txt file3.txt )
  2.  
  3. # Within the for loop, $file is the current file
  4. for file in "${fileList[@]}"
  5. do
  6.   echo "$file"
  7. done

Docker使用示例(5) – 查看容器信息

查看容器信息

  1. docker inspect -f ‘<format>’ <container>

查看网络设置

  1. docker inspect -f ‘{{ .NetworkSettings }}’ <container>

以json格式输出:

  1. docker inspect -f ‘{{ json.NetworkSettings }}’ <container>

获取IP地址

  1. docker inspect -f ‘{{ .NetworkSettings.IPAddress }}’ <container>
  2. <container>

当docker inspect输出多个同类的元素时,我们可以获取指定次序的元素,如获取Config.Env第一个元素:

  1. docker inspect –format ‘{{ index (index .Config.Env) 0 }}’ <container>

也可以获取元素的数量:

  1. docker inspect –format ‘{{ len .Config.Env }}’ <container>

Docker使用示例(4) – 清理容器和镜像

清理容器

清理已停止的容器:

  1. docker rm $(docker ps -qa)

清理所有容器,包括正在运行的和停止的:

  1. docker rm -f $(docker ps -qa)

删除僵死容器

  1. docker rm $(docker ps –all -q -f status=dead)

删除已退出的容器

  1. docker rm $(docker ps –all -q -f status=exited)

清理镜像

清理未生成过容器的镜像

  1. docker rmi $(docker images -qa)

清理所有镜像

  1. docker rmi -f $(docker images -qa)

清理没有tag的镜像

  1. docker images -q -f dangling=true | xargs –no-run-if-empty –delim=’n’ docker rmi

Docker使用示例(3) – 调试容器

打印日志

通过跟踪日志调试正在运行的程序是一种较少干扰的方法。下面的例子相当于在容器中执行了tail -f some-application.log命令。

  1. docker logs –follow –tail 10 7786807d8084

如果你的日志里没有包含时间戳,可以添加–timestamps标志。

监控资源占用

监控系统资源使用情况是一种比较有效找到资源占用过多的程序的方法。下面的例子与通常使用的top命令一样。

  1. docker stats

可以监控几个指定容器的资源:

  1. docker stats 7786807d8084 7786807d8085

Docker统计显示如下信息:
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
7786807d8084 0.65% 1.33 GB / 3.95 GB 33.67% 142.2 MB / 57.79 MB 46.32 MB / 0 B
默认情况下,docker stats命令显示的是容器的id,这对于识别容器没什么帮助,如果你想显示容器的名称,可以使用:

  1. docker stats $(docker ps –format ‘{{.Names}}’)

监控容器里的进程

下面的例子相当于传统的ps命令:

  1. docker top 7786807d8084

可以监控指定的进程,如faux:

  1. docker top 7786807d8084 faux

或者获取以root运行的进程列表:

  1. docker top 7786807d8084 -u root