Shell 编程 Tips

Posted by Matt Reach on May 26, 2017

日常工作中,特别是我搭建远程打包环境的过程中用到了不少 Shell 命令,这里记录下,省得以后再去查询,浪费时间。

内置变量

以下结果来自 Mac OS 10.12,提示:这些内置变量是以美元符号开头的,内置变量也是变量,在 Shell 脚本里面取变量的值均需以美元符号开头。

  • $SHELL : 查看当前终端使用是哪种 shell: /bin/bash
  • $HOME : 登陆用户主目录: /Users/xuqianlong
  • $PATH : 截取一小段来看下: /opt/local/bin:/opt/local/sbin,分号是分隔符;在终端输入命令就能执行的原理其实是,按照顺序遍历 PATH 路径,直到找到该命令,如果找不到就输出:-bash: ee: command not found (这里的 -bash 是当前 shell 类型) 学习过 Java 的同学肯定都知道 Path 的概念。
  • $USER : 当前登陆用户名称: xuqianlong
  • $OSTYPE : 操作系统类型: darwin16
  • $MACHTYPE : CPU架构及系统类型: x86_64-apple-darwin16
  • $LANG : 语言类型: zh_CN.UTF-8

使用 env 命令可查看当前的环境变量,这个命令太厉害了,上面提到的好多内置变量的值都包括了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
MANPATH=/Users/xuqianlong/.nvm/versions/node/v4.0.0/share/man:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/man:/usr/local/share/man:/usr/share/man:/opt/X11/share/man:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man
NVM_IOJS_ORG_VERSION_LISTING=https://iojs.org/dist/index.tab
rvm_bin_path=/Users/xuqianlong/.rvm/bin
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/bash
TERM=xterm-256color
TMPDIR=/var/folders/d2/bt0v98895vd53lcx9w_spzdw0000gn/T/
NVM_PATH=/Users/xuqianlong/.nvm/versions/node/v4.0.0/lib/node
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.f7R2BOD9jj/Render
TERM_PROGRAM_VERSION=377
TERM_SESSION_ID=75991873-8124-4503-8BA0-F10EB73C0D84
NVM_DIR=/Users/xuqianlong/.nvm
USER=xuqianlong
_system_type=Darwin
rvm_path=/Users/xuqianlong/.rvm
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.SZQYe5MXKe/Listeners
__CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34
rvm_prefix=/Users/xuqianlong
PATH=/Library/Frameworks/Python.framework/Versions/3.5/bin:/opt/local/bin:/opt/local/sbin:/Users/xuqianlong/.nvm/versions/node/v4.0.0/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Users/xuqianlong/libwebp-0.5.1-mac-10.9/bin:/Users/xuqianlong/bin:/Users/xuqianlong/.rvm/bin
NVM_NODEJS_ORG_MIRROR=https://nodejs.org/dist
PWD=/Users/xuqianlong
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home
LANG=zh_CN.UTF-8
_system_arch=x86_64
XPC_FLAGS=0x0
_system_version=10.12
XPC_SERVICE_NAME=0
rvm_version=1.27.0 (latest)
SHLVL=1
HOME=/Users/xuqianlong
LOGNAME=xuqianlong
NVM_BIN=/Users/xuqianlong/.nvm/versions/node/v4.0.0/bin
NVM_IOJS_ORG_MIRROR=https://iojs.org/dist
DISPLAY=/private/tmp/com.apple.launchd.0jBYczqYJG/org.macosforge.xquartz:0
SECURITYSESSIONID=186a6
_system_name=OSX
_=/usr/bin/env

基础语法

if

  • 文件是否存在

    1
    2
    3
    4
    5
    6
    
    if [ -f $last_commit_date_txt ];then
          last_commit_datestamp=$(cat $last_commit_date_txt)
          echo 'xql last_commit_datestamp:'$last_commit_datestamp
          last_commit_date=${last_commit_datestamp% *}
          last_commit_date=`expr $last_commit_date + 1`
    fi
    
  • 文件夹是否存在

    1
    2
    
    if [ -d $last_commit_folder ];then
    fi
    
  • 文件内容是否为空

    1
    2
    3
    4
    5
    
    if [ `cat $commit_info_txt | wc -m` -eq 0 ];then
        echo 'file is empty.'
    else
        echo 'file is not empty!'
    fi
    
  • 当做字符串比较

    1
    2
    3
    
    if [[ "abc" == $num ]];then
      echo 'num is abc'
    fi
    
  • 参数是否为空

    1
    2
    3
    
    if [[ -z "$num" ]];then
      echo 'num is nil'
    fi
    
  • 当做数字比较

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    if [[ 0 -eq $num ]];then
      echo 'num is 0'
    fi
      
    if [[ 0 -ne $num ]];then
      echo 'num is not equal 0'
    fi
      
    if [[ $num -gt 0 ]];then
      echo 'num is greater than 0'
    fi
      
    if [[ $num -lt 0 ]];then
      echo 'num is less than 0'
    fi
      
    if [[ $num -ge 0 ]];then
      echo 'num is greater than or equal 0'
    fi
      
    if [[ $num -le 0 ]];then
      echo 'num is less than or equal 0'
    fi
    

case

1
2
3
4
5
6
7
8
9
10
11
case $e in
  abc)
      echo "abc"
      ;;
  d | e)
      echo "$e"
      ;;
  *)
      echo "not abc and d and e"
      ;;
esac

for 循环

1
2
3
4
LIBS="A B C"
for lib in $LIBS; do
    echo "$lib"
done

传递含有空格的参数

1
2
ps="xxx zzz"
./build.sh "'$ps'"

算术运算

1
2
3
4
5
///使用 expr 外部程式
a=12;
b=19;
result=`expr $a + $b`
result=$(($a + $b))

字符串截取

  • % 截取,以空格举例:

    1
    2
    3
    
    str="1234444 +8000 +ddd"
    echo ${str% *}
    ///1234444 +8000
    

    从右往左截取,遇到第一个空格为止(空格也会截取);

  • ## 截取,以 / 为例:

    1
    2
    3
    
    str="/Users/qianlongxu/Documents/build_local.sh"
    echo ${str##*/}
    ///build_local.sh
    

    从左往右删除,直到删掉最后一个 / 为止;

  • 参考:https://www.cnblogs.com/zwgblog/p/6031256.html

函数返回值

1
2
3
4
5
6
7
8
9
10
function test(){
    echo "a";
    echo "b";
    echo "c";
    return 10;
}

r=$(test);
echo $?;
echo $r;

运行结果是:

1
2
10
a b c

把返回值改成非整数值,会报错:

1
2
test.sh: line 5: return: 10.1: numeric argument required
test.sh: line 5: return: a: numeric argument required

把返回值改成非[0~255]的整数值,会因为溢出,而舍弃高位,比如:

1
2
3
返回 256 ,实际接收到的是 0;
返回 257 ,实际接收到的是 1;
这个也很好理解,因为返回只暂用了一个字节,相当于 c 语言里的 unsigned char !

正因为这个返回值是个无符号整形,所以很多时候并不是我们想要的,因此这个返回值,都是用来告诉调用者内部是否正常执行的,如果执行出错了就返回一个非 0 值!

  • 编程时是需要返回值的,怎么办?

一种方法是像上面的例子一样,使用 echo 打印,其缺点是可能你只需要最后一个 echo 的打印值,中间的调试日志并不是你想要的,我曾经也遇到了这个问题!本来程序执行的没问题,就在中间加了一个函数调用,结果那个函数里有 echo 日志,导致了一个问题。

如果遇到这种情况,建议不要使用 echo 作为返回值,echo 正常用来写日志,而是使用全局变量!调用者使用全局变量拿到这个值;或者就是封装一个打印日志的方法,将日志重定向到文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function test(){
    echo "a";
    echo "b";
    echo "c";
    DOWNLOAD_URL='return abc';
    return '2';
}

test;
echo $?;
echo $DOWNLOAD_URL;

结果:
qianlongxu:Desktop qianlongxu$ sh test.sh
a
b
c
2
return abc

这里有个有趣的问题,如果你还想获取echo打印的返回值,你会发现全局变量的值取不到!

1
2
3
4
5
6
7
8
9
r=$(test);
echo $?;
echo $r;
echo $DOWNLOAD_URL;

结果:
qianlongxu:Desktop qianlongxu$ sh test.sh
2
a b c

常见命令

uname

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  ~ uname -m
x86_64
➜  ~ uname -n
qianlongxu
➜  ~ uname -p
i386
➜  ~ uname -r
20.6.0
➜  ~ uname -s
Darwin
➜  ~ uname -v
Darwin Kernel Version 20.6.0: Wed Jun 23 00:26:31 PDT 2021; root:xnu-7195.141.2~5/RELEASE_X86_64
➜  ~ uname -a    
Darwin qianlongxu 20.6.0 Darwin Kernel Version 20.6.0: Wed Jun 23 00:26:31 PDT 2021; root:xnu-7195.141.2~5/RELEASE_X86_64 x86_64

ps

用 ps 命令可以查看进程相关信息,哪些进程正在运行和运行状态、进程是否结束、进程有没有僵死、哪些进程占用了过多地资源等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
qianlongxu:~ qianlongxu$ ps
  PID TTY           TIME CMD
 2771 ttys000    0:00.07 -bash
 2836 ttys001    0:00.06 -bash
 1398 ttys004    0:00.10 -bash

qianlongxu:~ qianlongxu$ echo $$
2771

qianlongxu:~ qianlongxu$ ps -f
  UID   PID  PPID   C STIME   TTY           TIME CMD
  501  2771  2770   0  8:45上午 ttys000    0:00.07 -bash
  501  2836  2835   0  8:45上午 ttys001    0:00.06 -bash
  501  1398  1397   0  3:55下午 ttys004    0:00.10 -bash

qianlongxu:~ qianlongxu$ ps -e
  PID TTY           TIME CMD
    1 ??         0:51.90 /sbin/launchd
   51 ??         0:04.78 /usr/libexec/UserEventAgent (System)
 ///此处省略好多行
 2770 ttys000    0:00.04 login -pf qianlongxu
 2771 ttys000    0:00.07 -bash
 2900 ttys000    0:00.00 ps -e
 2835 ttys001    0:00.01 login -pf qianlongxu
 2836 ttys001    0:00.06 -bash
 1397 ttys004    0:00.02 login -pf qianlongxu
 1398 ttys004    0:00.10 -bash

///查看进程关系
qianlongxu:~ qianlongxu$ ps -f
  UID   PID  PPID   C STIME   TTY           TIME CMD
  501  2987  2986   0  9:00上午 ttys000    0:00.06 -bash
qianlongxu:~ qianlongxu$ zsh
qianlongxu%  
qianlongxu% ps -f
  UID   PID  PPID   C STIME   TTY           TIME CMD
  501  2987  2986   0  9:00上午 ttys000    0:00.07 -bash
  501  3102  2987   0  9:04上午 ttys000    0:00.03 zsh

重定向

使用重定向,可以轻松的提供输入,改变输出;

  • 输出重定向:

    • ”>” 覆盖文本

      1
      2
      3
      4
      5
      6
      7
      8
      
      echo "abc" > "aa.txt"
      echo "abc" > "aa.txt"
      echo "abc" > "aa.txt"
      //查看 aa.txt 文件,内容是:
      abc
          
      不想再执行命令时打印日志到控制台时,可以将日志重定向到 >/dev/null
      有的时候连错误日志也想屏蔽,那么可以这么写:/dev/null 2>&1
      
    • ”»” 追加文本

      1
      2
      3
      4
      5
      6
      7
      
      echo "abc" >> "aa.txt"
      echo "abc" >> "aa.txt"
      echo "abc" >> "aa.txt"
      //查看 aa.txt 文件,内容是:
      abc
      abc
      abc
      
  • 输入重定向:

    1
    2
    3
    4
    
    ///读取 aa.txt 文件的第一行,然后打印
    read line < aa.txt;echo $line
    ///结果是:
    abc
    

col

保存 man 命令查看的帮助信息

1
2
# 将 codesign 命令的帮助文档存储到桌面 codesign.txt 里
man codesign | col -b  > ~/Desktop/codesign.txt

bash 种类

查看机器上安装了几种 shell

  • cat /etc/shells
1
2
3
4
5
6
7
8
9
10
# 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
  • ls /bin/*sh ,结果跟上面是一样的
  • chsh -s /bin/zsh 修改用户 shell,重启终端生效

管道

这是一个强大的 shell 命令,可将输出结果作为另一个程序的输入,符号 : “|” ,比如查看当前目录,并且按字母顺序排列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ls | sort

Applications
Desktop
Documents
Downloads
GitBook
Library
Movies
Music
Pictures
Public
apache-tomcat-7.0.64
bin
cstudy
cworkspace

可以看出先排大写字母,后排小写字母,原因是大写字母的 ASCLL 比小写的小了 32,即:A + 32 = a ,A = 65.

cp

  • 复制文件,如果目标存在则会覆盖; 将 debugly 目录下的 init.sh 文件拷贝到 d2 目录下,debugly、d2 都在当前执行目录下; d2 文件夹必须存在!否则 cp 之后会生成一个 d2 的可执行文件!其内容是 init.sh 文件的内容。
1
cp debugly/init.sh d2

复制文件夹,如果目标存在则会覆盖; 将 debugly 目录下的所有文件都拷贝到 d2 目录下;d2 文件夹必须存在!否则 cp 执行失败。

1
cp -r debugly/init.sh d2

注:cp 命令不会复制 .开头的文件或者文件夹,除了 “.” 和 “..” 和 “.DS_Store” 这三个之外!一般情况下这是我们想要的结果。

scp

两台主机之间复制文件(夹),第一个参数是源文件,第二个参数是目的地。

  • 复制文件

    文件已经存在时会覆盖掉。

1
2
3
4
5
6
## scp form to
scp /Users/qianlongxu/Downloads/id_rsa.pub crown@110.117.40.176:~/id_rsa.pub
## 不带文件名时,保持原有文件名
scp /Users/qianlongxu/Downloads/id_rsa.pub crown@110.117.40.176:~/
## 重命名
scp /Users/qianlongxu/Downloads/id_rsa.pub crown@110.117.40.176:~/new_rsa.pub
  • 复制文件夹
1
2
3
4
5
## scp -r from to
## 将本机 qr-code 文件夹复制到远程 html 目录下;包括 qr-code 文件夹本身!文件已存在时会覆盖!
scp -r /Users/qianlongxu/Downloads/qr-code crown@110.117.40.176:/opt/www/html/
## 将本机 qr-code 文件夹里的内容复制到远程 qr-code 目录下;文件已存在时不会覆盖!
scp -r /Users/qianlongxu/Downloads/qr-code crown@110.117.40.176:/opt/www/qr-code/

注意:

1、复制文件夹时,scp只能增不能减:比如 qr-code 里删除了某个文件,scp 到远程主机后,远程主机上的文件不会被删除!

ssh

  • 登陆远程服务器:

    1
    2
    3
    
    ssh crown@110.117.40.176
    ///输入密码
    sohuxxx
    
  • 免密码登录

    可以把客户机的公钥追加到远程打包机器上的authorized_keys文件中,实现自动验证,无需泄露服务器密码

    1
    2
    3
    4
    
    ///将公钥追加到这个服务器~/.ssh/authorized_keys文件末尾 
    ssh-copy-id -i jenkins@110.117.40.195
    ///输入密码
    sohuxxx
    

    当主机不支持 ssh-copy-id 命令时,可手动添加到 authorized_keys 文件末尾。

  • 执行命令

    删除掉远程服务器桌面上的 xx 目录:

    1
    2
    
    Remote_dir='~/Desktop/xx'
    ssh root@12.11.193.18 "rm -rf ${Remote_dir}"
    
  • Here Document

    需要执行大量的脚本时,可通过 Here Document 的形式,将命令写成多行:

    1
    2
    3
    4
    5
    
    ssh -tt "root@$ip" <<EOF            
    cd "$SERVER_DIR/$name"
    ./upgrade.sh
    exit      
    EOF
    

    注意实际执行过程是,先执行 EOF 之间的内容,如果使用到了变量会被替换,然后再执行。

    如果要关闭变量替换功能,可将 «EOF 改为 «‘EOF’ 。

  • 执行需要交互的命令

    当远程登录主机A后,然后通过 ssh 远程其他机器执行时,默认情况下不会创建 TTY,也就无法进行交互,当执行到需要交互的命令时就会断开, 导致命令执行中断,报错如下:

    Pseudo-terminal will not be allocated because stdin is not a terminal.

    Here Document 形式的命令是交互式的,此时需要加上 -tt 参数,强制开启 TTY:

    1
    2
    3
    4
    5
    
    ssh -tt "root@$ip" <<EOF            
    cd "$SERVER_DIR/$name"
    ./upgrade.sh
    exit      
    EOF
    

kill

Address already in use 是经常遇到的问题,只能找到占用该端口的进程然后杀掉!

有一次我修改 Rakefile 的时候,修改不当导致没能处理 INT 信号,后果就是按下 ctrl + c 的时候没有将该子线程杀死,然后再次启动服务时就会报错:

1
jekyll 3.3.0 | Error:  Address already in use - bind(2) for 127.0.0.1:4000

为了解决这个问题就需要找到当前占用 4000 端口的进程,然后将其杀死;步骤是:

1
2
3
4
5
6
# 先查下占用 4000 端口的进程 id
xuqianlong$ lsof -i tcp:4000
COMMAND   PID       USER   FD   TYPE            DEVICE SIZE/OFF NODE NAME
ruby    49753 xuqianlong   10u  IPv4 0xebc4350e453a6ff      0t0  TCP localhost:terabase (LISTEN)
# 然后杀死该进程
xuqianlong$ kill 49753

netstat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[@110.116.189.228 /data/ifox/upgrade]# netstat -tlunp | grep nginx
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      13884/nginx         
[@110.116.189.228 /data/ifox/upgrade]# netstat -tlunp | grep nginx
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      13884/nginx         
[@110.116.189.228 /data/ifox/upgrade]# lsof -i tcp:80
COMMAND   PID   USER   FD   TYPE     DEVICE SIZE/OFF NODE NAME
nginx   13884   root    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13950 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13951 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13952 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13953 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13954 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13955 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13956 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13957 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13958 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13959 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13960 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13961 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13962 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13963 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13964 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13965 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13966 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13967 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13968 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13969 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13970 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13971 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13972 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13973 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13974 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13975 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13976 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13977 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13978 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13979 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13980 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13981 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13982 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13983 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13984 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13985 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13986 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13987 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13988 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13989 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13990 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13991 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13992 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13993 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13994 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13995 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13996 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)
nginx   13997 nobody    7u  IPv4 2169262441      0t0  TCP *:http (LISTEN)

curl

功能强大的网络工具,支持的协议众多,包括:DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP。最简单的是 GET 请求:

1
curl http://debugly.cn/dist/json/test.json

使用 HEADER 请求,仅返回 http 头部信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
curl -I http://debugly.cn/dist/json/test.json

HTTP/1.1 200 OK
Server: GitHub.com
Content-Type: application/json; charset=utf-8
Last-Modified: Thu, 03 Aug 2017 15:36:20 GMT
Access-Control-Allow-Origin: *
Expires: Sat, 05 Aug 2017 08:58:43 GMT
Cache-Control: max-age=600
X-GitHub-Request-Id: C99E:118EE:5EABC0:656C5C:5985866B
Content-Length: 6257
Accept-Ranges: bytes
Date: Sat, 05 Aug 2017 08:51:00 GMT
Via: 1.1 varnish
Age: 136
Connection: keep-alive
X-Served-By: cache-nrt6126-NRT
X-Cache: HIT
X-Cache-Hits: 1
X-Timer: S1501923060.014934,VS0,VE1
Vary: Accept-Encoding
X-Fastly-Request-ID: 0dc32470ef01fa9b0672d3d38edc904b9836debc

这个文档有详细的介绍:https://curl.haxx.se/download.html

  • 下载文件

在当前目录下递归创建 themes/hexo-theme-yaris 目录,然后下载zip包,解压到指定文件夹后重命名

1
mkdir -p "./themes/hexo-theme-yaris" && curl -L https://codeload.github.com/debugly/hexo-theme-yaris/zip/master | tar xj -C "./themes/hexo-theme-yaris" --strip-components 1

路径处理

  • 获取脚本所在目录
1
2
3
4
    dir=$(
    cd $(dirname $0)
    pwd
    )
  • 获取文件名(按/分割后的最后一部分)
1
2
3
4
5
6
    name=$(basename "/Users/matt/_posts/ijk.tar")
    # name is ijk.tar
    name=$(basename "/Users/matt/_posts/")
    # name is _posts
    name=$(basename "/Users/matt/_posts")
    # name is _posts
  • 获取文件名且不包括后缀名
1
2
3
4
    name=$(basename "/Users/matt/_posts/ijk.tar" .tar)
    # name is ijk
    name=$(basename -s .tar "/Users/matt/_posts/ijk.tar")
    # name is ijk

chown

使用 Homebrew 安装、更新软件时提示没有权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
==> make frameworkinstallextras PYTHONAPPSDIR=/usr/local/Cellar/python@3.9/3.9.0
Error: The `brew link` step did not complete successfully
The formula built, but is not symlinked into /usr/local
Could not symlink bin/2to3
Target /usr/local/bin/2to3
already exists. You may want to remove it:
  rm '/usr/local/bin/2to3'

To force the link and overwrite all conflicting files:
  brew link --overwrite python@3.9

To list all files that would be deleted:
  brew link --overwrite --dry-run python@3.9

Possible conflicting files are:
/usr/local/bin/2to3 -> /Library/Frameworks/Python.framework/Versions/3.7/bin/2to3
/usr/local/bin/idle3 -> /Library/Frameworks/Python.framework/Versions/3.7/bin/idle3
/usr/local/bin/pydoc3 -> /Library/Frameworks/Python.framework/Versions/3.7/bin/pydoc3
/usr/local/bin/python3 -> /Library/Frameworks/Python.framework/Versions/3.7/bin/python3
/usr/local/bin/python3-config -> /Library/Frameworks/Python.framework/Versions/3.7/bin/python3-config
Error: Permission denied @ dir_s_mkdir - /usr/local/Frameworks

之前遇到这个问题都是直接使用管理员权限即可,现在不行了,报错如下:

1
2
3
4
5
sudo brew link --overwrite python@3.9              
Password:
Error: Running Homebrew as root is extremely dangerous and no longer supported.
As Homebrew does not drop privileges on installation you would be giving all
build scripts full access to your system.

这时需要看下具体问题,比如上面遇到的问题是 /usr/local/Frameworks 没有权限,使用 chown 给用户添加 ownership 权限即可;

1
2
3
4
5
6
7
8
sudo chown -R $(whoami) /usr/local/Frameworks
chown: /usr/local/Framework: No such file or directory
# 原来这个目录不存在,那么创建一个吧:
mkdir -p /usr/local/Framework
sudo chown -R $(whoami) /usr/local/Frameworks
# 再次执行,成功啦
brew link --overwrite python@3.9 
Linking /usr/local/Cellar/python@3.9/3.9.0_1... 5 symlinks created

git

  • git submodule update –init –recursive

  • git log –date=format:’%Y-%m-%d %H:%M:%S’ -3

  • git log –date=format:’%a %A %b %B %c %d %H %I %j %m %M %p %S %U %w %W %x %X %y %Y %z’ –pretty=format:’%cd’ -3
  • git log –date=format:’%Y-%m-%d %H:%M:%S’ –after=’2018-03-23 20:44:06’

  • git log –date=format:’%S’ –pretty=format:’%cd’ -1 #获取最后一次提交时间秒数

  • 查看此次拉取远程之后,都有哪些提交记录

    1
    2
    3
    4
    5
    
    last_commit_datestamp=$(git log --date=raw --pretty=format:'%cd' -1)
    last_commit_date=${last_commit_datestamp% *}
    last_commit_date=`expr $last_commit_date + 1`
    git pull
    git log --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'<tr><td>%an</td><td>%s</td><td>%cd</td></tr>'  --after "'$last_commit_date'" -n 200
    
  • git修改user.name和user.email

    1
    2
    3
    4
    5
    
    //查看
    git config --list
    //修改
    git config --global user.name "name"
    git config --global user.email "email"
    
  • git clone -b $branch $repos $WORKSPACE

  • git branch –set-upstream-to=origin/$branch $branch

  • 指定仓库目录

    1
    2
    3
    4
    5
    6
    
    localPath="../.hexo-theme-yaris"
    alias gitC='git -C $localPath'
      
    gitC add *
    gitC commit -m "your msg"
    gitC push origin master
    

Git-基础-查看提交历史

eval

1
2
3
4
5
enc=xbm
xbm='monow'
eval fmts='$'$enc
echo "fmts:$fmts"
# fmts:monow

date

1
2
3
4
5
6
7
$(date +'Theme updated:%Y-%m-%d %H:%M:%S')
Theme updated:2018-04-16 17:52:24

# 后台执行

```bash
nohup /path/to/yourProgram &

统计文件(夹)

  • 统计文件夹下文件个数,包括子文件

    ls -lR | grep "^-"| wc -l

  • 统计文件夹下目录个数,包括子目录

    ls -lR | grep "^d"| wc -l

zip & tar

不带目录压缩,举例: 将 ./a 目录下的文件压缩成 zip 包,解压后不包含 a 目录,如果 a 里面有文件夹则保持原有层级;(不要使用 mac 自动的解压工具,否则解压后都会放到 a 目录里!搜索后是否包含目录,可以到 windows 下查看。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function mr_zip(){
    full_path=$1

    folder_name=${full_path##*/}
    zip_name="${folder_name}.zip"

    cp=$PWD    
    cd "$full_path"
    zip -ryq ../"$zip_name" ./*
    mr_ckr $? $LINENO
    cd ..
    zip_Path="${PWD}/${zip_name}"
    cd $cp
    echo "${zip_Path}"
}

zip_file=$(mr_zip "${path}")

先将目录里所有后缀名为 framework 的文件打包成 sdk.tar,然后用 gzip 压缩,生成 sdk.tar.gz 压缩的压缩包:

tar -czf sdk.tar.gz *.framework

abc 作为密码加密压缩:

1
zip -P abc your.zip yourpath

sed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
///将 fn 文件里的 'VV' 替换成 ${version}
sed -i '' "s/VV/${version}/" "${fn}"

///将 zip_url 变量里的 / 替换成 \/ ; 管道处理后赋值给 escape_url ; 这里转义了,所以不容易理解;
escape_url=$(echo "${zip_url}" | sed "s/\//\\\\\//g")
escape_url=$(echo "${zip_url}" | sed 's/\//\\\//g')sed 's/\//\\\//g' 展开看下:

sed 's/ \/ / \\\/ /g'; \/ 是 / 的转义, \\\ 的转义; /g 是全局替换;

//用 : 分割更加容易理解:
escape_url=$(echo "${zip_url}" | sed "s:/:\\\/:g")
escape_url=$(echo "${zip_url}" | sed 's:/:\\\/:g')

https://stackoverflow.com/questions/13971113/how-to-replace-on-path-string-with-using-sed

mdls 查看文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
➜  ~ mdls /Users/qianlongxu/Desktop/SOHO-logo.685e6e1f.png 
_kMDItemDisplayNameWithExtensions      = "SOHO-logo.685e6e1f.png"
kMDItemBitsPerSample                   = 32
kMDItemColorSpace                      = "RGB"
kMDItemContentCreationDate             = 2020-06-18 02:02:08 +0000
kMDItemContentCreationDate_Ranking     = 2020-06-18 00:00:00 +0000
kMDItemContentModificationDate         = 2020-06-18 02:02:08 +0000
kMDItemContentModificationDate_Ranking = 2020-06-18 00:00:00 +0000
kMDItemContentType                     = "public.png"
kMDItemContentTypeTree                 = (
    "public.png",
    "public.image",
    "public.data",
    "public.item",
    "public.content"
)
kMDItemDateAdded                       = 2020-06-18 02:02:08 +0000
kMDItemDateAdded_Ranking               = 2020-06-18 00:00:00 +0000
kMDItemDisplayName                     = "SOHO-logo.685e6e1f.png"
kMDItemDocumentIdentifier              = 0
kMDItemFSContentChangeDate             = 2020-06-18 02:02:08 +0000
kMDItemFSCreationDate                  = 2020-06-18 02:02:08 +0000
kMDItemFSCreatorCode                   = ""
kMDItemFSFinderFlags                   = 0
kMDItemFSHasCustomIcon                 = (null)
kMDItemFSInvisible                     = 0
kMDItemFSIsExtensionHidden             = 0
kMDItemFSIsStationery                  = (null)
kMDItemFSLabel                         = 0
kMDItemFSName                          = "SOHO-logo.685e6e1f.png"
kMDItemFSNodeCount                     = (null)
kMDItemFSOwnerGroupID                  = 20
kMDItemFSOwnerUserID                   = 501
kMDItemFSSize                          = 9936
kMDItemFSTypeCode                      = ""
kMDItemHasAlphaChannel                 = 1
kMDItemInterestingDate_Ranking         = 2020-06-18 00:00:00 +0000
kMDItemKind                            = "PNG图像"
kMDItemLastUsedDate                    = 2020-06-18 02:02:08 +0000
kMDItemLastUsedDate_Ranking            = 2020-06-18 00:00:00 +0000
kMDItemLogicalSize                     = 9936
kMDItemOrientation                     = 0
kMDItemPhysicalSize                    = 12288
kMDItemPixelCount                      = 3360
kMDItemPixelHeight                     = 42
kMDItemPixelWidth                      = 80
kMDItemUseCount                        = 5
kMDItemUsedDates                       = (
    "2020-06-17 16:00:00 +0000"
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
mdls /usr/local/var/www/ffmpeg-test/\ 测试视频格式/6视频格式.3gp
_kMDItemDisplayNameWithExtensions      = "6视频格式.3gp"
com_apple_metadata_modtime             = 625658506
kMDItemContentCreationDate             = 2020-10-29 10:01:46 +0000
kMDItemContentCreationDate_Ranking     = 2020-10-29 00:00:00 +0000
kMDItemContentModificationDate         = 2020-10-29 10:01:46 +0000
kMDItemContentModificationDate_Ranking = 2020-10-29 00:00:00 +0000
kMDItemContentType                     = "public.3gpp"
kMDItemContentTypeTree                 = (
    "public.3gpp",
    "public.movie",
    "public.audiovisual-content",
    "public.data",
    "public.item",
    "public.content"
)
kMDItemDateAdded                       = 2021-02-19 08:10:36 +0000
kMDItemDateAdded_Ranking               = 2021-02-19 00:00:00 +0000
kMDItemDisplayName                     = "6视频格式.3gp"
kMDItemDocumentIdentifier              = 0
kMDItemFSContentChangeDate             = 2020-10-29 10:01:46 +0000
kMDItemFSCreationDate                  = 2020-10-29 10:01:46 +0000
kMDItemFSCreatorCode                   = ""
kMDItemFSFinderFlags                   = 0
kMDItemFSHasCustomIcon                 = (null)
kMDItemFSInvisible                     = 0
kMDItemFSIsExtensionHidden             = 0
kMDItemFSIsStationery                  = (null)
kMDItemFSLabel                         = 0
kMDItemFSName                          = "6视频格式.3gp"
kMDItemFSNodeCount                     = (null)
kMDItemFSOwnerGroupID                  = 20
kMDItemFSOwnerUserID                   = 501
kMDItemFSSize                          = 1428616
kMDItemFSTypeCode                      = ""
kMDItemInterestingDate_Ranking         = 2020-10-29 00:00:00 +0000
kMDItemIsScreenCapture                 = 1
kMDItemKind                            = "3GPP影片"
kMDItemLastUsedDate                    = 2020-10-29 10:01:46 +0000
kMDItemLastUsedDate_Ranking            = 2020-10-29 00:00:00 +0000
kMDItemLogicalSize                     = 1428616
kMDItemPhysicalSize                    = 1429504
kMDItemScreenCaptureGlobalRect         = (
    453,
    0,
    900,
    596
)
kMDItemScreenCaptureType               = "selection"
kMDItemUseCount                        = 2
kMDItemUsedDates                       = (
    "2020-10-28 16:00:00 +0000"
)

有兴趣可以看下这个链接: https://stackoverflow.com/questions/23564995/how-to-modify-a-global-variable-within-a-function-in-bash

export

可将变量暴露到子 shell 环境,正常情况下声明的变量只作用于当前文件,在当前shell文件里调用其他文件或者命令时,想要使其访问到某个变量时, 可使用 export 命令。

export XC_PLAT=”$PLAT”

export 一个方法使用 -f 参数即可:

export -f init_env

alias

善于利用别名,可以提升效率!

可以在 ~/.bashrc(使用 zsh 的 编辑这个文件 ~/.zshrc)文件里增加命令或者脚本的别名通过 alias 命令,举例:

1
2
3
4
5
alias autossh="/Users/qianlongxu/Documents/auto-ssh/login.sh"

alias proxy='export all_proxy=socks5://127.0.0.1:7890'

alias unproxy='unset all_proxy'

保存 rc 文件后,重新打开一个终端窗口后者 source ~/.bashrc 也行;

后续想要执行 /Users/qianlongxu/Documents/auto-ssh/login.sh 不用输入这么长的命令了,直接输入 autossh 即可。

sips

Mac带的一个命令行工具,缩放图片超级简单:

1
2
3
4
5
6
7
8
9
10
# 导出jpeg格式,支持 jpeg | tiff | png | gif | jp2 | pict | bmp | qtif | psd | sgi | tga 共11种格式
sips -s format jpeg ./201512/img201512272145-0.png -o ./201512/img201512272145-0.jpg
# 宽高最大不超过 1200像素
sips -Z 1200 *.jpg
# 将 png 后缀的图片文件,生成后缀为 jpg 的图片文件
find . -name "*.png" | awk -F / '{b=$0;sub(/png/,"jpg");printf "sips -s format jpeg %s -o %s\n",b,$0}' | bash
# 删除 png 后缀的文件
find . -name "*.png" | xargs rm -f
# 将 JPG 后缀的文件改为 jpg
find . -name "*.JPG" | awk -F / '{b=$0;sub(/JPG/,"jpg");printf "mv %s %s\n",b,$0}' | bash

zsh 相关

1、AutoJump

1
2
3
4
5
6
7
8
# https://github.com/wting/autojump
brew install autojump
vim ~/.zhsrc 
# 追加 autojump
plugins=(autojump) 
# 最后一行添加
# autojump configure by xql;
[[ -s $(brew --prefix)/etc/profile.d/autojump.sh ]] && . $(brew --prefix)/etc/profile.d/autojump.sh

查看进入过的目录,进入过的就可以通过部分文件名跳转了: ~/Library/autojump/autojump.txt

2、zsh-syntax-highlighting

命令高亮,默认绿色高亮:

1
2
3
4
5
6
# https://github.com/zsh-users/zsh-syntax-highlighting
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
vim ~/.zhsrc
# 追加 
plugins=(zsh-syntax-highlighting)
source ~/.zshrc

3、zsh-autosuggestions

命令提示,按键盘 → 补全

1
2
3
4
5
6
# https://github.com/zsh-users/zsh-autosuggestions
git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
vim ~/.zhsrc
# 追加
plugins=(zsh-autosuggestions)
source ~/.zshrc

NodeJS

  • 查看版本
1
2
3
4
bogon:debugly xuqianlong$ node -v 
v8.9.3
bogon:debugly xuqianlong$ npm -v
5.5.1

NPM (node package manager) 是 Nodejs 的包管理器,用于 Node 插件管理(包括安装、卸载、管理依赖等)

  • npm init : 在当前目录下引导创建一个package.json文件,包括名称、版本、作者这些信息等
  • npm start :启动模块
  • npm ls : 查看安装的模块
  • npm uninstall [-g] [--save-dev] : 卸载插件
  • npm update [-g] [--save-dev] : 更新插件
  • npm update [–save-dev] : 更新全部插件
  • npm help : 查看帮助
  • npm list : 查看当前目录已安装插件

遇到过的问题:

1
2
3
4
hexo d -g
ERROR Deployer not found: git
///解决方法:
npm install --save hexo-deployer-git

express 框架

1
2
3
#http://www.expressjs.com.cn/starter/generator.html
npm install express-generator -g
express myapp

Hexo

  • 预览 : hexo server,简写 hexo s
  • 生成 : hexo generate,简写 hexo g
  • 发布 : hexo deploy,简写 hexo d
  • 清理 : hexo clean

Python

  • python 2 部署临时 Web 服务器

    1
    2
    3
    4
    
    # 默认8000端口
    python -m SimpleHTTPServer
    # 使用 9000 端口
    python -m SimpleHTTPServer 9000
    
  • python 3 部署临时 Web 服务器

    1
    2
    3
    4
    
    # 默认8000端口
    python -m http.server
    # 使用 9000 端口
    python -m http.server 9000
    

Vim 编辑器

命令模式

  • 显示行号:set number
  • 插入:i
  • 插入新行:o
  • 粘贴:p
  • 删除行: dd
  • 删除字符:dw
  • 复制行:yy
  • 复制字符:yw
  • 移动光标: h (左) j(下)k(上)l(右)可以使用数字相乘,比如 10h的意思是向左移动10个字符
  • 移动到行首:^
  • 移动到行尾:$
  • 光标回退一个单词:b
  • 光标前进一个单词:w
  • 跳到文档开始:gg
  • 跳到文档结尾:G
  • 保存:wq
  • 不保存:q!
  • 搜索:/关键词 搜到之后,使用 n/N 切换搜到的关键字
  • 全部替换:%s/old/new
  • 交互式替换:%s/old/new/gc
  • 撤销:u
  • 多窗口: split/vsplit
  • 关闭窗口: close
  • 多窗口跳转:ctrl + ww / ctrl +w(h/j/k/l)
  • 上下窗口调整大小:ctrl + w(-/=)

运维相关

1、查询服务器当前连接数

1
2
[@bx_140_25 ~]# netstat -ant | grep -c ESTABLISHED
45

2、查询服务器某个端口当前连接数

1
2
[@bx_140_25 ~]# netstat -ant | grep ':443' | grep -c ESTABLISHED
15

3、查看磁盘空间

1
2
3
4
5
[@yz18-120-118.localdomain /]# df -h 
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        12G   12G   16M 100% /
tmpfs           7.8G  4.0K  7.8G   1% /dev/shm
/dev/vdb1       296G  191G   90G  69% /data

4、查看文件夹占用空间

1
2
3
4
5
6
7
8
#递归列出所有子目录大小
du -h
#深度为1,意味着只列出一级子目录大小
du -h --max-depth=1
#深度为0,意味着只列当前目录大小;等同于 du -sh
du -h --max-depth=0
获取文件大小 (单位 MB): 
IPA_Size=$(du -sm $IPA_Path | awk '{print $1}')

5、根据端口号查询程序

1
2
3
lsof -i tcp:30446
COMMAND     PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
statserve 16965 root   50u  IPv4 493378354      0t0  TCP 101.1.7.21:30446->16.9.14.14:mysql (ESTABLISHED)

6、监控程序

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

# every 3 secs
step=3

for (( i = 0; i < 60; i = (i+step) )); do
    PROCESS=`ps -e | grep -v 'grep' | grep dispatch`
    if [ -z "$PROCESS" ]; then
        echo "start dispatch"
        /opt/feihu/dispatch/start.sh
    fi
    sleep $step
done

小案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#进入到bash脚本所在目录
THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd)
cd "$THIS_DIR"

#md5加密,然后取前20
pwd=(echo -n {version} | md5 | cut -d" " -f 1 | cut -c 1-20)

#判断目录下是否存在某种文件

if ls ${ARCH_PC_DIR}/*.pc >/dev/null 2>&1;then
fi

#删除空文件(大小为0)
find . -type f -size 0 | xargs -n 1 rm -f

iOS build 号自增

1
2
3
4
5
6
7
8
9
# info.plist路径
project_infoplist_path="/proj.ios_mac/ios/game-inhouse.plist"
#取版本号
appVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${project_infoplist_path}")
#取build号
#buildNO=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" "${project_infoplist_path}")
#加1
buildNO=$(($buildNO+1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$project_infoplist_path"

字符串和枚举对应关系

1
2
3
4
5
6
7
8
LENS="English Chinese German Spanish Russian Korean French Japanese Portuguese Turkish Polish Catalan Dutch Arabic Swedish Italian"
for len in $LENS; do
echo "        case MRAudioTranslateLanuage_"${len}": "
echo "        {"
echo "            return @\"${len}\";"
echo "        }"
echo "            break;"
done