Shell命令(1)

24 7月

源码在这里Github(forked from shehaodan/full-shell-demo)

操作系统的内核(kernel)是真正操作硬件的,所以内核需要被保护,只能通过shell来和内核沟通。例如上面用的chmod,chown等命令都是独立的应用程序,可以通过shell(命令行模式)来操作这些应用程序。

之所以要学习命令行shell,因为各操作系统界面不一样,但各linux发行商提供的bash都是一样的(Linux的shell版本Bourne Again Shell,简称bash),所以学会命令行shell,可以一招鲜吃遍天。

  • 概述
  • 变量
  • 字符串
  • 数组
  • 内置命令
  • 数学计算
  • 条件语句

概述

Shell程序一般都是放在/bin或者/usr/bin目录下,当前Linux系统可用的Shell都记录在/etc/shells文件中:

> cat /etc/shells

# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

当前Linux的默认Shell:

> echo $SHELL
/bin/zsh

简单的脚本Github(forked from shehaodan/full-shell-demo)试一下:

#!/bin/bash
echo "Hello World !"
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"

# 执行 zsh first-test.sh
# 或 cd 进根目录,执行 ./zsh first-test.sh  (因为第一行#!/bin/bash,所以会用bash执行)
# 以上两种方式都是开新进程执行shell脚本,可以使用source命令在当前进程中执行脚本:source ./first-test.sh

变量

在Bash shell中,变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。

使用定义过的变量,只要在变量名前面加$即可。变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界

name='张三'
echo $name
echo "hello ${skill}"
# Shell是严格区分单双引号的,单引号里面是什么就输出什么,适用于纯字符串,无法用在变量的输出上。字符串里包含变量,参照上面用双引号

只读的变量需要加上readonly,删除变量用unset

#!/bin/bash

anoVar="unset test"
unset anoVar
echo $anoVar          # 什么都没输出

myVar="readonly test"
readonly myVar
myVar="change var"    # 执行到这里报错

局部变量要用local,否则默认都是全局变量:

#!/bin/bash

function func(){
    local a=99
    b=100
}

func       # 执行函数
echo $a    # 什么都没输出
echo $b    # 100

想在其他子进程中使用当前的变量(例如环境变量environment variable),需要export出去。

环境变量被创建时所处的Shell进程称为父进程,如果在父进程中再创建一个新的进程来执行Shell命令,那么这个新的进程被称作Shell子进程。当Shell子进程产生时,它会继承父进程的环境变量,即说环境变量可从父进程传给子进程,但只能向下传递而不能向上传递。

#!/bin/bash

a=22       #定义一个全局变量
echo $a    #在当前Shell中输出a,成功输出22

bash       #进入Shell子进程
echo $a    #在子进程中输出a,失败输出空

exit       #退出Shell子进程,返回上一级Shell

export a   #将a导出为环境变量
bash       #重新进入Shell子进程
echo $a    #在子进程中再次输出a,成功输出22
exit       #退出Shell子进程
exit       #退出父进程,结束整个Shell会话

运行shell脚本文件可以给它传递参数,脚本文件内部可以使用$n的形式来获取参数。

Shell脚本内部的函数没有形参和实参,即定义Shell函数时不能带参数,只能在调用函数时却传递参数,函数内部就也使用$n的形式获取参数。

#!/bin/bash

echo "do $0"
echo "name $1"
echo "age $2"
echo "params number $#"
echo "$*"
echo "$@"
echo "$?"
echo "process id $$"

> zsh ./variable/position-var.sh Tim 10
# do ./variable/position-var.sh
# name Tim
# age 10
# params number 2
# tim 10
# tim 10
# 0
# process id 16354

上面看出参数是从$1开始的,$0是特殊变量,特殊变量一览:

$0:当前脚本的文件名
$n(n>1):传递给脚本或函数的参数
$#:传递给脚本或函数的参数个数
$*:传递给脚本或函数的所有参数
$@:传递给脚本或函数的所有参数。当被双引号””包含时,$@ 与 $* 稍有不同
$?:上个命令的退出状态(成功返回 0,失败返回1),或函数的返回值。
$$:当前Shell进程 ID

例子中$*和$@的输出结果一样,不被双引号””包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。但是当它们被双引号””包含时:

$*:会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
$@:仍然将每个参数都看作一份数据,彼此之间是独立的。

#!/bin/bash

echo "print each param from \"\$*\""
for var in "$*"
do
    echo "$var"
done
echo "print each param from \"\$@\""
for var in "$@"
do
    echo "$var"
done

> zsh ./variable/special-var-1.sh 1 2 3 4
# print each param from "$*"
# 1 2 3 4
# print each param from "$@"
# 1
# 2
# 3
# 4

字符串

Shell是严格区分单双引号的,单引号里面是什么就输出什么,适用于纯字符串,无法用在变量的输出上。字符串里包含变量需要用双引号。

获取字符串长度:${#string_name}

url="https://zxljack.com/"
echo ${#url}    #20

拼接字符串只需要两个放在一起(中间不能有空格)即可:

#!/bin/bash

name="Site"
url="http://www.baidu.com/"
str1=$name$url                          # 中间不能有空格
str2="$name $url"                       # 如果被双引号包围,那么中间可以有空格
str3=$name": "$url                      # 中间可以出现别的字符串
str4="$name: $url"                      # 这样写也可以
str5="${name}Script: ${url}index.html"  # 需要给变量名加上大括号
echo $str1
echo $str2
echo $str3
echo $str4
echo $str5

# Sitehttp://www.baidu.com/
# Site http://www.baidu.com/
# Site: http://www.baidu.com/
# Site: http://www.baidu.com/
# SiteScript: http://www.baidu.com/index.html

字符串截取有两种方式:从左边开始计数时,起始数字是 0(这符合程序员思维)。从右边开始计数时,起始数字是 1(这符合常人思维)。计数方向不同,起始数字也不同,但不管从哪边开始计数,截取方向都是从左到右。

格式 说明 粒子
${string: start : length} 从字符串的左边第 start 个字符开始,向右截取 length 个字符
url="https://zxljack.com/"
echo ${url: 8: 7}
# zxljack
${string: start} 从字符串的左边第 start 个字符开始截取,直到最后
url="https://zxljack.com/"
echo ${url: 8}
# zxljack.com/
${string: 0-start :length} 从字符串的右边第 start 个字符开始截取,直到最后
url="https://zxljack.com/"
echo ${url:0-12}
# zxljack.com/
${string#*chars} 从字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符
url="https://zxljack.com/"
echo ${url#*//}
# zxljack.com/
${string##*chars} 从字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符
url="https://zxljack.com/"
echo ${url##*//}
# zxljack.com/
${string%chars*} 从字符串最后一次出现 chars* 的位置开始,截取chars * 左边的所有字符
url="https://zxljack.com/"
echo ${url%//*}
# https:
${string%%chars*} 从字符串第一次出现 chars* 的位置开始,截取 chars *左边的所有字符
url="https://zxljack.com/"
echo ${url%%//*}
# https:

数组

()圆括号来定义数组,数组元素间用空格来分隔,获取数组元素和长度:

${array_name[index]} # 获取数组的某个值、
${nums[*]}           # 获取数组的所有元素
${nums[@]}           # 获取数组的所有元素
${#array_name[@]}    # 获取数组长度
${#array_name[*]}    # 获取数组长度

#!/bin/bash
nums=(29 100 13 8 91 44)
echo ${nums[@]}      # 29 100 13 8 91 44
nums[10]=66          
echo ${nums[*]}      # 29 100 13 8 91 44 66
echo ${nums[4]}      # 8
echo ${#nums[*]}     # 10
echo ${#nums[@]}     # 10
echo ${#nums[10]}    # 2 

数组合并的思路是:先利用@*,将数组扩展成列表,然后再合并到一起。

array_new=(${array1[@]}  ${array2[@]})
array_new=(${array1[*]}  ${array2[*]})

#!/bin/bash
array1=(23 56)
array2=(99 "abc")
array_new=(${array1[@]} ${array2[*]})
echo ${array_new[@]}    # 23 56 99 abc

删除数组:

unset array_name[index]    # 删除数组元素
unset array_name           # 删除数组

内置命令

type来显示外部命令的路径,内置命令不显示路径:

> type cd
cd is a shell builtin

> type cal
cal is /usr/bin/cal

通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘I/O,还需要fork出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前Shell进程的一个函数。

::扩展参数列表,执行重定向操作
.:读取并执行指定文件中的命令(在当前 shell 环境中)
alias:为指定命令定义一个别名
bg:将作业以后台模式运行
bind:将键盘序列绑定到一个 readline 函数或宏
break:退出 for、while、select 或 until 循环
builtin:执行指定的 shell 内建命令
caller:返回活动子函数调用的上下文
cd:将当前目录切换为指定的目录
command:执行指定的命令,无需进行通常的 shell 查找
compgen:为指定单词生成可能的补全匹配
complete:显示指定的单词是如何补全的
compopt:修改指定单词的补全选项
continue:继续执行 for、while、select 或 until 循环的下一次迭代
declare:声明一个变量或变量类型
dirs:显示当前存储目录的列表
disown:从进程作业表中刪除指定的作业
echo:将指定字符串输出到 STDOUT,如果不希望换行,可以加上-n 参数
enable:启用或禁用指定的内建shell命令
eval:将指定的参数拼接成一个命令,然后执行该命令
exec:用指定命令替换 shell 进程
exit:以指定的退出状态码退出,接受一个0-255间的整数作为退出码。默认 0 表示成功,任何为非 0 的都表示执行失败
export:设置子 shell 进程可用的变量
fc:从历史记录中选择命令列表
fg:将作业以前台模式运行
getopts:分析指定的位置参数
hash:查找并记住指定命令的全路径名
help:显示帮助文件
history:显示命令历史记录
jobs:列出活动作业
kill:向指定的进程 ID(PID) 发送一个系统信号
let:计算一个数学表达式中的每个参数
local:在函数中创建一个作用域受限的变量
logout:退出登录 shell
mapfile:从 STDIN 读取数据行,并将其加入索引数组
popd:从目录栈中删除记录
printf:使用格式化字符串显示文本
pushd:向目录栈添加一个目录
pwd:显示当前工作目录的路径名
read:从 STDIN 读取一行数据并将其赋给一个变量
readarray:从 STDIN 读取数据行并将其放入索引数组
readonly:从 STDIN 读取一行数据并将其赋给一个不可修改的变量
return:强制函数以某个值退出,这个值可以被调用脚本提取
set:设置并显示环境变量的值和 shell 属性
shift:将位置参数依次向下降一个位置
shopt:打开/关闭控制 shell 可选行为的变量值
source:读取并执行指定文件中的命令(在当前 shell 环境中)
suspend:暂停 Shell 的执行,直到收到一个 SIGCONT 信号
test:基于指定条件返回退出状态码 0 或 1
times:显示累计的用户和系统时间
trap:如果收到了指定的系统信号,执行指定的命令
type:显示指定的单词如果作为命令将会如何被解释
typeset:声明一个变量或变量类型。
ulimit:为系统用户设置指定的资源的上限
umask:为新建的文件和目录设置默认权限
unalias:刪除指定的别名
unset:刪除指定的环境变量或 shell 属性
wait:等待指定的进程完成,并返回退出状态码

发表评论

电子邮件地址不会被公开。 必填项已用*标注