Skip to main content

shell-bash脚本收集

https://coolshell.cn/articles/19219.html https://github.com/Bash-it/bash-it

https://www.bilibili.com/video/BV14L4y157Bv?p=19&vd_source=c2f975abf353677cb814e38e073b6a75

https://github.com/wangdoc/bash-tutorial https://github.com/jaywcjlove/shell-tutorial

1. shell script

https://zhuanlan.zhihu.com/p/264346586

1.1. 片段收集

处理输入参数


while [ "$1" != "" ]; do
case $1 in
-s ) shift
SERVER=$1 ;;
-d ) shift
DATE=$1 ;;
--paramter|p ) shift
PARAMETER=$1;;
-h|help ) usage # function call
exit ;;
* ) usage # All other parameters
exit 1
esac
shift
done

简单ui界面


#!/bin/bash
# Bash Menu Script Example

PS3='Please enter your choice: '
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"
do
case $opt in
"Option 1")
echo "you chose choice 1"
;;
"Option 2")
echo "you chose choice 2"
;;
"Option 3")
echo "you chose choice $REPLY which is $opt"
;;
"Quit")
break
;;
*) echo "invalid option $REPLY";;
esac
done

远程服务器运行本地脚本


#无参数
ssh user@server 'bash -s' < local.script.sh

#有参数
ssh user@server ARG1="arg1" ARG2="arg2" 'bash -s' < local_script.sh

1.1.1. springboot 程序部署

#!/bin/bash
APP_NAME=$1

usage() {
echo "Usage: sh run.sh xxx.jar [start|stop|restart|status]"
exit 1
}

is_exist(){
pid=`ps -aux|grep $APP_NAME |grep java |grep -v grep|awk '{print $2}'`
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}

start(){
is_exist
if [ $? -eq 0 ]; then
echo "${APP_NAME} is already running. pid=${pid}"
else
nohup java -jar ${APP_NAME} >logs.log 2>&1 &
sleep 1
status
fi
}

stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
else
echo "${APP_NAME} is not running"
fi
}

status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}

restart(){
stop
sleep 5
start
}

case "$2" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac




1.1.2. 判断文件是否存在

#shell 判断文件夹存在
if [ -d "${your_dir_name}" ]; then
fi
# 判断文件夹不存在
if [ ! -d "${your_dir_name}" ]; then
fi

#shell 判断文件存在
if [ -f "${your_file_path}" ]; then
echo "${your_file_path} exists."
fi
# shell 判断文件不存在
if [ ! -f "${your_file_path}" ]; then
echo "${your_file_path} not exists."
fi

1.1.3. 调用 Python

# >>> shell call Python

#有时候某些操作用shell实现比较麻烦,这时可以考虑用python实现。
#再利用shell调用python的实现
#以下代码保存在 utils.py
from datetime import datetime, timedelta
def get_yesterday():
yesterday_date = datetime.now() - timedelta(days=1)
yesterday_date_str = yesterday_date.strftime("%Y-%m-%d")
return yesterday_date_str
#以下代码保存在 run.sh中
#shell调用python函数
yesterday_date=`python -c "import utils; print(utils.get_yesterday())"`
echo ${yesterday_date}

1.1.4. 判断是否安装

if [ -x "$(command -v git)" ]
then
echo "SUCCESS: Git is installed"
else
echo "ERROR: Git does not seem to be installed."
echo "Please download Git using your package manager or over https://git-scm.com/!"
exit 1
fi

1.1.5. 当前路径


# 在命令行状态下单纯执行 $ cd `dirname $0` 是毫无意义的。因为他返回当前路径的"."
# 这个命令写在脚本文件里才有作用,他返回这个脚本文件放置的目录,并可以根据这个目录来定位所要运行程序的相对位置
echo $(cd `dirname $0`;pwd) # 直接使用 pwd 会报错: 无法找到命令

1.1.6. 判断服务是否存活


#!/bin/bash

# detect runing service

echo -e "I will detect runing services."
testing=$(netstat -tuln | grep ":80")
if [ "${testing}" != "" ]; then
echo "WWW is runing."
fi

testing=$(netstat -tuln | grep ":22")
if [ "${testing}" != "" ]; then
echo "SSH is runing."
fi

testing=$(netstat -tuln | grep ":21")
if [ "${testing}" != "" ]; then
echo "FTP is runing."
fi

testing=$(netstat -tuln | grep ":25")
if [ "${testing}" != "" ]; then
echo "MAIL is runing"
fi

1.1.7. 判断当前系统


# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac

1.1.8. mvnw wrapper

#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------

# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------

if [ -z "$MAVEN_SKIP_RC" ] ; then

if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi

if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi

fi

# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac

if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi

if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done

saveddir=`pwd`

M2_HOME=`dirname "$PRG"`/..

# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`

cd "$saveddir"
# echo Using m2 at $M2_HOME
fi

# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi

# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi

if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi

if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi

if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi

if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi

CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher

# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {

if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi

basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}

# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}

BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi

##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi

if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi

else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################

export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"

# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi

# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS

WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain

exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

1.2. 执行方式

1.2.1. 在命令行指定解释器执行

同时在 sub shell 中执行, 脚本 内容不影响当前 shell

  • bash xxx.sh
  • sh xxx.sh sh 是 到 bash 的链接

1.2.2. 在脚本中指定解释器

在 sub shell 中执行

  • ./xxx.sh - 需要 #!/usr/bin/bash
  • /root/xxx/yy/zzz.sh - need #!/usr/bin/bash

1.2.3. 在当前 shell 中执行

常用来刷新配置文件

  • . xxx.sh
  • source xxx.sh

1.3. 各种括号 简写

https://mp.weixin.qq.com/s/B2fALisq_D8bazpD1hb0Fg TODO

1.4. 特殊的参数变量

$0  # 获取脚本名称, 或者说路径
# 在命令行状态下单纯执行 这个命令是没有意义的, 因为他返回当前路径 "."
# 只有在脚本中执行才有意义, 返回脚本路径
cd `dirname $0`

$1 $2 $3 ... #表示传递给脚本的参数 第一个 第二个, 第三个

$# # 传入参数总个数

$* # 获取脚本所有参数
# 等同 $@
"$*" # 接收所有参数为 单个字符串 , 就像 "$1 $2 $3 ...", 把参数作为一个字符串整体(单字符串)返回

$@ # 等同 $*
"$@" # 接收所有参数为 多个独立字符串, 推荐
for N in "$@"
do
echo $N
done



1.5. 特殊的状态变量


# 前一个命令的返回值, 执行成功为 0,
# 如 echo "" &>/dev/null && echo $? 将打印 0
$?

host=baidu.com
ping -c 1 $host &>/dev/null
if [ $? -eq 0 ]; then
echo "$host is up"
else
echo "$host is down"
fi

# 写脚本时有用
# 特殊变量$?记录上一次命令的返回值
[ $? -ne 0 ] && { ... }





$$ #记录当前shell本身的 PID (脚本运行的当前进程ID号)
#可以使用$$变量扩展出当前进程的 PID 作为临时文件名,PID 在计算机中都是唯一的,所以绝不会重复

# $! Shell上次执行的进程的 pid

$_ #上次命令传递的最后一个参数




1.6. 语法

严格模式 tips

使用 -e 参数,如:set -e 或是 #!/bin/sh -e,这样设置会让你的脚本出错就会停止运行,这样一来可以防止你的脚本在出错的情况下还在拼拿地干活停不下来。
使用 -u 参数,如: set -eu,这意味着,如果你代码中有变量没有定义,就会退出。

对一些变理,你可以使用默认值。如:${FOO:-'default'}

尽量不要使用 ; 来执行多个命令,而是使用 &&,这样会在出错的时候停止运行后续的命令。

对于一些字符串变量,使用引号括起,避免其中有空格或是别的什么诡异字符。

1.6.1. 变量赋值 空格语法的坑

# 普通变量: 不能在父子 shell 间传递
# 全局变量/环境变量 : 可以在父子 shell 传递 , 通过 export 导出的变量
# 局部变量/方法变量

# shell 默认定义的所有变量类型都是 字符串

var=2 #正确
var = 2 #错误
#bash: var: command not found

# 条件语句中[],中括号内外、运算符两边一定要加空格
var=2
if [ var = 2 ]; then
echo "对了"
else
echo "错误"
fi

# expr命令 expr后面、运算符两边一定要加空格
expr 1 + 2

# 运算操作
$((2+4)) # 代码可读差
expr 2 + 4 # 两边一定要加空格
$[2=4] #可读好, 对空格也没严格要求
let a=2+4 # 赋值
let a++ # 自增

# 反引号内的命令会被执行
name=`echo "hello"`
echo $name # hello

1.6.2. 数据类型

1.6.2.1. 字符串 打印

#!/bin/bash

PATH=${PATH}:/bin:/sbin:~/bin
export PATH

# -e 激活转义字符
echo -e "hello world"
exit 0

# 单引号 会原样输出
# 双引号 会变量替换 -> 推荐





# 字符串拓展
echo {one,two,three}file
onefile twofile threefile

echo {one,two,three}{1,2,3}
one1 one2 one3 two1 two2 two3 three1 three2 three3

cp /very/long/path/file{,.bak}
# 给 file 复制一个叫做 file.bak 的副本

rm file{1,3,5}.txt
# 删除 file1.txt file3.txt file5.txt

mv *.{c,cpp} src/
# 将所有 .c 和 .cpp 为后缀的文件移入 src 文件夹

# 特殊命令!$会替换成上一次命令最后的路径
# 没有加可执行权限
$ /usr/bin/script.sh
zsh: permission denied: /usr/bin/script.sh

$ chmod +x !$
chmod +x /usr/bin/script.sh

# 特殊命令!*会替换成上一次命令输入的所有文件路径
# 创建了三个脚本文件
$ file script1.sh script2.sh script3.sh

# 特殊命令!!,可以自动替换成上一次使用的命令
$ apt install net-tools
E: Could not open lock file - open (13: Permission denied)

$ sudo !!
sudo apt install net-tools

# 给它们全部加上可执行权限
$ chmod +x !*
chmod +x script1.sh script2.sh script3.sh

# 可以在环境变量CDPATH中加入你常用的工作目录,当cd命令在当前目录中找不到你指定的文件/目录时,会自动到CDPATH中的目录中寻找
# 这样就免了经常写完整的路径名称,节约不少时间
$ export CDPATH='~:/var/log'
# cd 命令将会在 ~ 目录和 /var/log 目录扩展搜索

$ pwd
/home/labuladong/musics
$ cd mysql
cd /var/log/mysql
$ pwd
/var/log/mysql
$ cd my_pictures
cd /home/labuladong/my_pictures


# 搜索历史命令
# 过滤出所有包含 config 字段的历史命令
$ history | grep 'config'
找到后, 只要使用!+ 你想重用的命令编号即可运行该命令。

# yes命令自动输入字符y进行确认
$ yes | your_cmd

1.6.2.2. 数组

myarray=()  #定义一个空数组
myarray=(1 2 3 4 5) #全部元素为数字
myarray=(one two three four five) #全部元素为字符串
myarray=(1 two 3 three 4 five) #元素由数值和字符串构成
myarray=(1 two 3 “a phrase” 5 now)

# 赋值
myarray[0]=1
myarray[2]=test

# 获取
arr_element2=${arr_name[2]}

# 长度
arr_len=${#arr_name[*]}
${#arr_name[@]}
# 元素长度
arr_elem_len=${#arr_name[index]}

# 打印
echo ${arr[@]} # 将元素作为一个整体打印
for ele in ${arr[*]} # 迭代每个元素打印
do echo $ele
done

# 数组分片访问
arr_name=(1 2 3 test go now)
echo ${arr_name[@]:1:2},将输出2 3

# 删除
删除数组某个元素:unset arr_name[index]
删除整个数组:unset arr_number

# 合并
arr=()
arr+=(1)
echo ${arr[@]}# 1
arr+=(2 tes 3 xx)
echo ... # 1 2 tex 3 xx


ip_array=("192.168.1.1" "192.168.1.2" "192.168.1.3")
for ip in ${ip_array[*]}
do
if [ $ip = "192.168.1.1" ]; then
port="7777"
else
port="22"
fi
ssh -t -p $port $user@$ip "remote_cmd"
done

1.6.3. 读取标准输入


#!/bin/bash
# "read" usage

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "input first name: " firstname
read -p "input last name: " lastname
echo -e ${firstname} ${lastname}

#############################

#!/bin/bash

# create 3 files, which named by user's input and date command;
echo -e "I will create 3 files."
read -p "input file name please: " file_name
file_name=${file_name:-"filename"}

date1=$(date --date="2 days ago" +%Y%m%d) # 前两天
date2=$(date --date="1 days ago" +%Y%m%d) # one day ago
date3=$(date +%Y%m%d) # today

file1=${file_name}${date1}
file2=${file_name}${date2}
file3=${file_name}${date3}

# touch

touch ${file1}
touch ${file2}
touch ${file3}

1.6.4. 数值运算



#!/bin/bash

# desc: + - * / % 数值运算 $((xxx*xxx))
# declare -i total=${first_num}*${sec_num}
# <==> total=$((a*b))

PATH=${PATH}:/bin:/sbin:~/bin
export PATH

echo -e "input 2 numbers and I will cross them.\n"

read -p "first number: " first_num
read -p "second number: " sec_num

total=$((${first_num}*${sec_num}))
echo -e "result: ${total}"

1.6.5. 条件判断

#!/bin/bash

# 判断
# test xx==xx <==> [ "xx" == "xx" ] (tips: [] 内部的变量都用双引号抱起来以防止变量中有空格; 每个部分都要隔开)

# -e (exist)
# -f (file) 是否是文件(file exist)
# -d (directory) ; 是否是文件夹

# -rwx (readable? writeable? exectable?) ;

# -eq (equal)
# -ne (not equal)

# test -z "xxx" (zero) 判断string是否为空 0/null;
# test -n "xxx" (not zero, 可省略) 不为0;
# test "xxx"=="yyy" ("="也可)是否相等;
# test "xxx"!="yyy" 是否不等;

# -a (and)
# -o (or)
# ! (not)

PATH=${PATH}:/bin:/sbin:~/bin
export PATH

echo -e "I'll test wether the file name you input exist. "
read -p "input a file name: " file_name

test -z ${file_name} && echo -e "you must input a valid file name" && exit 0
test ! -e ${file_name} && echo "the file ${file_name} does not exist" && exit 0

test -f ${file_name} && type="regular file"
test -d ${file_name} && type="directory"
test -r ${file_name} && perm="readable"
test -w ${file_name} && perm="${perm} writable"
test -x ${file_name} && perm="${perm} excutable"

echo -e "it's a ${type}"
echo -e "and the permissions are: ${perm}"

##########################################

#!/bin/bash

# desc: make choice 判断的另一种写法

read -p "input Y/N: " yn
[ "${yn}"=="Y" -o "${yn}"=="y" ] && echo -e "ok, continue." && exit 0
[ "${yn}"=="N" -o "${yn}"=="n" ] && echo -e "oh, break." && exit 0
echo -e "I don't know what you mean." && exit 0

#######################################


#!/bin/bash

# "if then" usage
# [ "${param}" == "xxx" -o "${param}" == "yyy" ] <==> [ "${p}" == "xxx" ] || [ "${p}" == "yyy" ]

read -p "input y/n: " p
if [ "${p}" == "y" ] || [ "${p}" == "Y" ]; then
echo "ok, continue."
exit 0
elif [ "${p}" == "n" ] || [ "${p}" == "N" ]; then
echo "oh, interrupt"
else
echo "I don't know what you mean." && exit 0
fi

##################################################

1.6.6. 条件判断-case 语法


#!/bin/bash

# case xx in 用法

case ${1} in
"hello")
echo "hello, h a y"
;;
"")
echo "must input parameter, eg> ${0} someword"
;;
*)
echo "usage: ${0} hello"
;;
esac

1.6.7. 条件判断-多路分支语法

action="a"
if [ "${action}" = "a" ]; then
#操作
elif [ "${action}" = "b" ]; then
#操作
elif [ "${action}" = "c" ]; then
#操作
else
#操作
fi

1.6.8. 默认值

file_name=$1
file_name=${file_name:-"no_name.sh"}

1.6.9. 函数


#!/bin/bash

# 定义 函数

function printit(){
echo "Your choice is $1" # 这个 $1 是 函数后面跟着的参数
}
case $1 in # 这里的 $1 是脚本后面的参数
"one")
printit 1 # 请注意, printit 指令后面还有接参数!
;;
"two")
printit 2
;;
"three")
printit 3
;;
*)
echo "Usage $0 {one|two|three}"
;;
esac

###############################

# 循环 while do done, until do done 条件满足则种植循环, for var in value1 value2 value3... do done

#!/bin/bash

# "cut", function, for var in ... do done

PATH=${PATH}:/bin:/sbin:~/bin
export PATH

names=$(cut -d ":" -f1 /etc/passwd)
for name in ${names}
do
id ${name}
finger ${name}
done

########################################################

network="192.168.1" # 先定丿一个网域的前面部分!
for sitenu in $(seq 1 100) # seq 为 sequence(连续) 的缩写
do
# 底下的程序在 ping 的回传值是正确的还是失败的!
ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
# 开始显示结果是正确的 (UP) 还是错误的没有连通 (DOWN)
if [ "$result" == 0 ]; then
echo "Server ${network}.${sitenu} is UP."
else
echo "Server ${network}.${sitenu} is DOWN."
fi
done

###################################

# 循环加判断

# 1. 先看看这个目录是否存在啊?
read -p "Please input a directory: " dir
if [ "$dir" == "" -o ! -d "$dir" ]; then
echo "The $dir is NOT exist in your system."
exit 1
fi

# 2. 开始测试档案啰~
filelist=$(ls $dir) # 列出所有在该目录下的文件名
for filename in $filelist
do
perm=""
test -r "$dir/$filename" && perm="$perm readable"
test -w "$dir/$filename" && perm="$perm writable"
test -x "$dir/$filename" && perm="$perm executable"
echo "The file $dir/$filename's permission is $perm "
done

##########################################

# for ((xx; xx; xx)) do done

for (( i=1; i<=$nu; i=i+1 ))
do
s=$(($s+$i))
done
echo "The result of '1+2+3+...+$nu' is ==> $s"

1.7. debug

sh [-nvx] scripts.sh

-n :不要执行 script,仅查询语法的问题;
-v :再执行 sccript 前,先将 scripts 的内容输出屏幕上;
-x :输出一行脚本, 执行这一行,debug 常用

sh -x xx.sh

1.8. 学习几个demo

这是一个 Java 程序的启动脚本

#!/bin/sh
## java env
export JAVA_HOME=/usr/local/jdk1.7.0_55
export JRE_HOME=$JAVA_HOME/jre

## exec shell name
EXEC_SHELL_NAME=sys-service\.sh
## service name
SERVICE_NAME=zhousw-sys-service
SERVICE_DIR=/usr/local/workspace/sys-service
JAR_NAME=$SERVICE_NAME\.jar
PID=$SERVICE_NAME\.pid

cd $SERVICE_DIR

case "$1" in

start)
nohup $JRE_HOME/bin/java -Xms256m -Xmx512m -jar $JAR_NAME >/dev/null 2>&1 &
echo $! > $SERVICE_DIR/$PID
echo "#### start $SERVICE_NAME"
;;

stop)
kill `cat $SERVICE_DIR/$PID`
rm -rf $SERVICE_DIR/$PID
echo "#### stop $SERVICE_NAME"

sleep 5

TEMP_PID=`ps -ef | grep -w "$SERVICE_NAME" | grep -v "grep" | awk '{print $2}'`
if [ "$TEMP_PID" == "" ]; then
echo "#### $SERVICE_NAME process not exists or stop success"
else
echo "#### $SERVICE_NAME process pid is:$TEMP_PID"
kill -9 $TEMP_PID
fi
;;

restart)
$0 stop
sleep 2
$0 start
echo "#### restart $SERVICE_NAME"
;;

*)
## restart
$0 stop
sleep 2
$0 start
;;

esac
exit 0



2. 正则&文本格式化

2.1. 正则表达式和语言系统有关系

2.2. 正则中的特殊符号

2.3. grep进阶用法

grep基础用法

正则表示法中的特殊符号

  • grep -n 'key' <file> 搜寻key
  • grep -vn 'key' <file> 反向选择
  • dmesg | grep 'eth'找到核心信息的eth那行
  • dmesg | grep -n --color=auto 'eth' 关键词显色, 显示行号
  • dmesg | grep -n -A3 -B2 --color=auto 'eth' 前2行和后3行一起显示
  • grep -n 't[ea]st' <file>搜寻即可字符(test 或 tast)
  • grep -n '[^g]oo' regular_express.txt 不为指定字符
  • grep -n '[^a-z]oo' regular_express.txt 不为某个字符范围
  • grep -n '[0-9]' regular_express.txt 取得有数字的行
  • grep -n '^the' regular_express.txt以指定字符开头
  • grep -n '^[a-z]' regular_express.txt 开头为小写字符
  • grep -n '\.$' regular_express.txt 以指定字符结束
  • grep -n 'o\{2\}' regular_express.txt 指定字符出现次数

如果希望设定grep自动关键词显色, 可修改~/.bashrc , 加上一行:alias grep='grep --color=auto', 然后source ~/.bashrc生效

2.4. 延伸正则表示法

egrep

2.5. sed

还可以将数据进行取代、删除、新增、抓取特定行

  • nl /etc/passwd | sed [-e] '2,5d' 带行号列出, 并删除2-5行(sed后的动作需要引号)
  • nl /etc/passwd | sed '2a drink tea' 第2行后添加文字
  • nl /etc/passwd | sed '2i drink tea'行前添加文字
  • sed增加多行, 需要+enter跳脱
  • nl /etc/passwd | sed '2,5c No 2-5 number' 取代2-5行
  • nl /etc/passwd | sed -n '5,7p' 抓取5-7行, 同head -n 7 | tail -n 2

sed的行内格式化: sed 's/要被取代的字符串/新字符串/g'

  • ifconfig eth0 | grep 'inet addr' | sed 's/^.*addr://g' | sed 's/Bcast.*$//g'获取ip(替换功能)
  • sed -i 's/匹配串/替换成的内容/g' /xxx 替换,直接修改原始档案

2.6. awk行内格式化

格式: awk '条件类型1{动作 1} 条件类型 2{动作 2} ...' filename; 默认的行内分隔符为空格或者tab; 变量可以直接用, 无需加$符号

  • last -n 5 | awk '{print $1 "\t" $3}' 没有条件,只有动作; $1代表第一栏, $2表示第二栏...; $0表示一整栏

awk的内建变量:

  • last -n 5| awk '{print $1 "\t lines: " NR "\t columes: " NF}' 和printf联用, printf的内容用双引号
  • awk 'BEGIN{FS=":"} $3 < 10 {print $1 "\t " $3} 不用默认的分隔符号

2.7. printf格式化打印

  • printf '%s\t %s\t %s\t \r\n' $(cat /xxx) 格式化打印

2.8. 文件对比工具

diff: 以行为对比单位

cmp: 字节为单位对比

patch: 对比升级

  • diff -Naur passwd.old passwd.new > passwd.patch 制作差异补丁文件
  • patch -p0 < passwd.patch 更新
  • patch -R -p0 < passwd.patch 还原

-p0的解释见原始码&tarball

3. bash

3.1. 快捷键

Ctrl+u 清除当前命令行,命令输入了一半,不想继续,用delete太慢,Ctrl+c太笨,Ctrl+u 刚合适 Ctrl+k 清除光标后内容 Ctrl+w 清除光标前的一个单词 Ctrl+a 光标移至行首,很常用 Ctrl+e 光标移至行尾,很常用 Ctrl+l 清屏,等同于clear Ctrl+z 当前命令行执行程序挂起,想退出命令,又不想终止程序,可以使用这个命令

3.2. 几种 bash 环境 startup 配置文件对比

  • 交互式 shell: 在终端上执行,shell等待你的输入,并且立即执行你提交的命令, 如 登录、执行一些命令.

  • 非交互shell: 以shell script(非交互)方式执行, 当它读到文件的结尾EOF,shell也就终止了.

    “$-”变量的值(代表着当前shell的选项标志), 查看字符串中是否包含“i”选项(表示interactive shell)来区分交互式与非交互式shell

  • 登录shell:需要用户名、密码登录后才能进入的shell(或者在脚本中通过”–login”选项生成的shell), 称为一个会话

  • 非登录 shell: 不需要输入用户名和密码即可打开的Shell,例如:直接命令“bash”就是打开一个新的非登录shell, 通过 terminal 打开的 shell

对于bash, 登录shell(包括交互式登录shell和使用“–login”选项的非交互shell),它会首先读取和执行 /etc/profile全局配置文件, 接着执行 /etc/profile.d/目录下的脚本文件,然后依次查找 ~/.bash_profile、~/.bash_login 和 ~/.profile这三个配置文件, 然后 ~/.bashrc, 登出, 执行 ~/.bash_logout; su - root 就是切到 root 的登录 shell

在非登录shell里,只读取 ~/.bashrc (和 /etc/bash.bashrc、/etc/bashrc )文件,不同的发行版里面可能有所不同, 如果装了 zsh, 则先读取 .zshrc, 一般会在 .zshrc 中 source .bash_profile; su root 就是 切到 root nologin shell

3.3. 和 Python 交互


#!/bin/bash

ping -c 1 baidu.com &>/dev/null && echo "network ok" || echo "network error"

# eof 只是标识符, 可以随意定义
# <<- 为必须,
/usr/bin/python3 <<-eof
print("hello py")
eof

echo "hello bash"

3.4. 示例

https://github.com/jaywcjlove/shell-tutorial https://github.com/wangdoc/shell-tutorial/blob/master/docs/basics/variable.md



#!/bin/bash

echo "helloworld!"

# 运行
./demo.sh


# #################shell 变量

# - 环境变量,
# 区分大小写

# - shell变量
# PATH 决定了shell将到哪些目录中寻找命令或程序
# HOME 当前用户主目录
# HISTSIZE 历史记录数
# LOGNAME 当前用户的登录名
# HOSTNAME 指主机的名称
# SHELL 当前用户Shell类型
# LANGUGE  语言相关的环境变量,多语言可以修改此环境变量
# MAIL 当前用户的邮件存放目录
# PS1 基本提示符,对于root用户是#,对于普通用户是$
echo $HOME
echo ${HOME}

###############自定义变量 字符串#################
#!/bin/bash
#自定义变量hello
# 变量声明默认都是string类型
# 等号两边无空格, 若值中有空格, 用引号(单双均可)括起来
# [""]中的 [$] 可以保有原本特性, 如 var="lang is $LANG"
# ['']中的 [$] 仅仅是一般字符

#* 设定变量后需要`export <变量名>` 导出以在其他程序中使用
#* 系统变量一般为大写, 自定义变量为小写方便判断
#* `unset <变量名>` 取消变量设定
hello="hello world"
# or
hello='hello world'
echo $hello
echo "helloworld!"
echo 'hello - ${PATH}' # hello - ${PATH}

#获取字符串长度
name="SnailClimb"
# 第一种方式
echo ${#name} #输出 10
# 第二种方式
expr length "$name";

# 计算表达式

# 使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身:
expr 5 + 6 // 输出 11
expr 5+6 // 错误, 直接输出 5+6

# 对于某些运算符,还需要我们使用符号\进行转义
expr 5 * 6 // 输出错误
expr 5 \* 6 // 输出30

################## string 截取###################3


# 截取子串
#从字符串第 1 个字符开始往后截取 10 个字符
# 从 0 开始, 左闭右开
str="SnailClimb is a great man"
echo ${str:0:10} #输出:SnailClimb

# 查找子串
# 查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4, index 从 1 开始

# 根据表达式截取
# #: 表示左→右开始匹配,匹配最短的, 然后删除, 返回剩余的串
# ##: 表示左→右开始匹配,匹配最长的, 然后删除
# %: 表示右→左开始匹配,匹配最短的, 然后删除
# %%:表示右→左开始匹配,匹配最长的, 然后删除

#!bin/bash
#author:xiaoyu

var="http://www.runoob.com/linux/linux-shell-variable.html"

s1=${var%%t*}#h
s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h
s3=${var%%.*}#http://www
s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html
s5=${var##*/}#linux-shell-variable.html

####################shell数组############################

#!/bin/bash
array=(1 2 3 4 5);
# 获取数组长度
length=${#array[@]}
# 或者
length2=${#array[*]}
#输出数组长度
echo $length #输出:5
echo $length2 #输出:5
# 输出数组第三个元素
echo ${array[2]} #输出:3
unset array[1]# 删除下表为1的元素也就是删除第二个元素
for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5
unset arr_number; # 删除数组中的所有元素
for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容

############### 引入其他指令 #####################

# 引入其他指令, 借助反单引号 " \` ", 如
system=\`uname\`----\`uname -r\`
echo system # 显示Linux-----3.10.0-693.2.2.el7.x86_64 ;

# 另外还有一种方法:
system=$(uname)----$(uname -r)


3.5. 所有可用的shell

bash是linux可以使用的shell之一(zsh, dash...), 查看系统可以使用的全部shell可以vim /etc/shells, 见下图:

某个用户登录可以使用的shell, 记录在 /etc/passwd

bash优点:

  • 历史命令记录, 在 ${user.home}/.bash_history, 如root的在 ~/.bash_history

  • 命令/档案补全

  • alias, 如 alias lm='ls -al'

  • (job control, foreground, background. 后台运行

  • shell script

  • 支持通配符 *

type <cmd> 列出指令使用情况 可以当作which

3.6. 环境变量

  • env 查看所有环境变量(也可以用export)

    • declare -i number=$RANDOM*10/32768 定义随机数number, 范围是0---9
  • set 查看环境变量和自定义变量(也可以用declare)

  • 设定命令提示符PS1 echo $PS1
  • echo $$ 本shell 的 线程代号(PID)

  • echo $? 上个命令的返回值(一般上个指令正常结束返回0, 异常则返回错误代码)

  • export <自定义变量名> 自定义变量转为环境变量

    • 环境变量能被子程序识别(子程序继承了环境变量), 自定义变量无法被子程序识别(无法继承)
  • locale -a查看所有支持的语系

  • locale 查看本机的语系设定

3.7. 键盘读取read

  • read <自定义变量名> 读取键盘输入, 存入指定变量
  • read -p "Please keyin your name: " -t 30 named 带提示信息, 限制等待时间30s

3.8. declare,typeset声明变量类型

  • declare -i sum=100+300+50, 此时sum为450
  • declare -x <变量名> 转为环境变量 ,同 export xxxx
  • declare +x <环境变量名> 移除环境变量
  • declare -r <变量名> 声明为只读
  • declare -p sum 单独列出变量类型

3.9. 变量的删除取代

#:      表示左→右开始匹配,匹配最短的, 然后删除
##: 表示左→右开始匹配,匹配最长的, 然后删除
%: 表示右→左开始匹配,匹配最短的, 然后删除
%%:表示右→左开始匹配,匹配最长的, 然后删除

匹配方向有别,但是匹配表达式还是顺序的

取代看后面的图

var="http://www.runoob.com/linux/linux-shell-variable.html"

s1=${var%%t*}#h
s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h
s3=${var%%.*}#http://www
s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html
s5=${var##*/}#linux-shell-variable.html

3.10. 变量的测试和内容替换

判断变量是否存在

  • xxx=${xxx-yyy} 如果xxx未设定,则设为yyy
  • xxx=${xxx:-root} 如果未设定or为空串,设为yyy

更多见:

3.11. 命令别名和设定

  • 设定别名alias lm='ls -al | more'
  • 查看所有alias
  • 取消设定unalias lm

3.12. 命令历史

  • history 列出所有的命令历史
  • history 3 最近3个命令

3.13. 指令搜寻顺序

  1. 以相对/绝对路径执行命令,例如『 /bin/ls 』和『 ./ls 』;
  2. 由 alias 找到命令执行;
  3. 由 bash 内建的 (builtin) 命令来执行;
  4. 透过 $PATH 这个发量的顺序搜寻到第一个命令执行。

3.14. bash登录欢迎信息

  • cat /etc/issue查看进站信息
  • cat /etc/issue.net 查看使用telnet登录的欢迎信息

  • cat /etc/motd 大家登录都会看到的提示信息

3.15. bash的环境配置文件

login shell(输入帐号密码) 和 non-login shell(无需输入帐号密码就能登录)两种登录shell类型登录时读取的配置文件不同

login shell只读取两个配置文件:

  1. /etc/profile(系统的整体设定, 不要修改)

    • 每个使用者登录都会读取, 设定这些变量
* 此外, 还会读取外部的配置文件

* /etc/inputrc 设定一些热键
* /etc/profile.d/*.sh (多个), 规定了bash接口的颜色, 语系一些命令别名; 如果需要为所有的user设定命令别名则修改
* /etc/sysconfig/i18n 这个档案是由 /etc/profile.d/lang.sh 调用!这也是我们决定 bash 使用什么预习,LANG的设定
  1. ~/.bash_profile 或 ~/.bash_login 或 ~/.profile---------都是个人配置文件, 在1和1的下属配置文件都调用的才调用它

    • 3个文件之后有一个被读取, 以上是读取顺序, 一旦前者被读取, 后者就不会读取了

    • 以~/.bash_profile为例, 实际会读取~/.bashrc, 可以将自定义配置写入该文件, 通过source ~/.bashrc 读入配置; 或者. ~/.bashrc

login shell只读取~/.bashrc

此外, 还有其他的配置文件会影响bash

/etc/man.config

~/.bash_history

~/.bash_logout

3.16. 终端机的环境设定stty,set

stty -a列出所有按键和按键内容

比如设定 stty erase ^h 使用ctrl+h进行删除

3.17. 通配符和特殊符号

3.18. 数据流重导向

  • ll / > <file> 将数据输出到指定文件(数据会被覆盖)
  • ll / >> <file> 数据会被累加
  • 查看/home下是否有.bashrc档案存在find /home -name .bashrc, 有出错信息会直接输出到屏幕, 现在将正常结果和错误信息输出到不同文件find /home -name .bashrc > list_right 2> list_error
  • find /home -name .bashrc 2> /dev/null 将错误输出扔掉( /dev/null 可以吃掉任何导向这个装置的信息)
  • find /home -name .bashrc > list 2>&1 正确输出和错误输出写入同一个文件
  • cat > <file> 将键盘输入存入指定文件(ctrl+d结束输入)
  • cat > catfile < ~/.bashrc 新建立的catfile就是后者的复制
  • cat > catfile << "eof" 输入"eof"就结束输入(<<表示后面接的是结束字符)
  • 多重重导向 tee
* `last | tee last.list | cut -d " " -f1 ` 同时输出一份数据到两处地方
* ` ls -l /home | tee ~/homefile | more`同上
* ` ls -l / | tee -a ~/homefile | more ` 累加输出

3.19. &&和||

3.20. 管线命令

3.21. 截取命令

cut: 主要用于将每一行里的数据分解, 去除想要的分段

  • echo $PATH | cut -d ':' -f 5获取 PATH 内容,找出第五个路径
  • echo $PATH | cut -d ':' -f 3,5 找到第三个和第五个路径
  • export | cut -c 12- 每一行从第12个字符开始取

grep: 从多行中去除需要的行, 见grep进阶用法

  • last | grep 'root' 含有root的行
  • grep -v 'root' 没有root的行
  • grep --color=auto '<key>' <file> 在指定文件中寻找

3.22. 排序sort,wc,uniq

sort: sort将文件/文本的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出

对于-k: 格式:-k fstart.cstart, fend.cend,都是数字,从1开始,若有多重排序,后面再接 -k ...eg:http://wangchujiang.com/linux-command/c/sort.html

  • cat /etc/passwd | sort 默认排序
  • cat /etc/passwd | sort -t ':' -k 3 冒号分开,用第三栏排序
  • cat /etc/passwd | sort -t ':' -k 3 -n 以数字进行排序(默认为文字排序)

uniq: 将重复的数据只显示一行(必须在使用sort之后使用)

wc: 显示总字符, 总行数

3.23. 字符转换命令

tr: 删除 文字替换

  • last | tr '[a-z]' '[A-Z]' 所有的小写变为大写
  • cat /etc/passwd |tr -d ":" 将passwd中所有的[:]删除
  • cp /xxx /root/xxx && unix2dos /root/xxx 将/xxx复制到/root下并转成dos断行
  • cat /xxx | tr -d '\r' > /root/xxx删除/xxx中的换行符号

col: tab以空格显示

  • cat -A /xxx显示出所有按键符号
  • cat /etc/man.config | col -x | cat -A | more 显示tab为空格

expand: 替换tab为空格

split: 档案分割

xargs: 主要用于将前面的输出流格式化成输入参数

join, paste

3.24. 字符串

删库悲剧:

# https://mp.weixin.qq.com/s/arUO8dhdUa-L1evglIQiog

cur_path = `pwd`
new_cur_path = `${cur_path}/bbb` # 问题: `` 包围的代码会先执行, 但是 这里 反引号中的内容不是有效的命令, 返回空
rm -rf ${new_cur_path}/* 2>>/dev/null # 最终变成 rm -rf /*

# 改正: 将 `` 去掉, 或者再加上 ""
# 最好的办法是:
#如果目录不为空,才执行删除操作
if [ ${work_path} != "" ];then
rm -fr ${work_path}/*
fi

vim 使用

https://vim-adventures.com/ vim游戏 https://mp.weixin.qq.com/s/_XncbAGmfeWDSG04Ce51vA tips

https://vimjc.com/ blog https://github.com/wsdjeg/vim-galore-zh_cn 中文教程

  • 一般模式: 默认模式, 可移动光标, 删除, 复制粘贴; 无法编辑内容
  • 编辑模式: 一般模式中, 按下[i, o, a, r] 进入, 按下[Esc]退出
  • 命令模式: 一般模式中, 按下[:, /, ?]三个中的任何一个, 即可进入, 可 搜索, 保存, 离开vim/vi, 大量取代字符

比如想删除一行再进行编辑, 之前习惯 dd 再 insert, 其实有更简便的 S;
再比如 a = "xyz" 想更换双引号为单引号, 使用 r 来一个一个替代, 其实有更简便的: 光标在 xyz 中, cs"'(change surrounding " to ').


ZZ 替代 :wq, 无需回车
:x 保存退出




>> 浏览:

ctrl+d "向下"移动 【半页】
ctrl+u “向上”移动 【半页】
ctrl+f 向下一页
ctrl+b 向上一页
Ctrl+o 返回上一个位置
G 移动到【最后一行】
18G 移动到 18 行
:18 到第 18 行
gg 移动到【第一行】
enter 下一行有效字符行首 n+enter 向下移动n行行首
- 上一行有效行首

0 移动到行首
^ 移动到有字符的行首
g_ 到本行最后一个不是blank字符的位置。和 ^ 对应
$ 移动到行尾


w 下一个单词头部
W 下一个大单词头部
e 下一个单词尾部

% : 匹配括号移动,包括 (, {, [. (陈皓注:你需要把光标先移到括号上)
* 和 #: 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词(*是下一个,#是上一个)


( 向上跳转到空行和空行附近的一行
) 向下跳转到空行和空行附近的一行
{ } 和 ( ) 类似但不跳转空行的附近行
]] 跳转到下一个 {
[[ 跳转到上一个 {
][ 跳转下一个 }
[] 跳转到上一个 }
% 跳转到另一个花括号或圆括号

:E 浏览目录 也可以直接vim一个目录




>> 搜寻:

/keyword 回车 向下搜寻keyword, 按下【n】后, 继续向下搜寻下一个, 【N】表示向上搜寻; 找到目标后回车光标到达目标位置
?keyword 回车 向上搜寻
\<keyword\> 首尾精确匹配 “\>” 是一个特殊的记号,表示只匹配单词末尾。类似地,”\<” 只匹配单词的开头

:set ic(ignorecase缩写) 忽略大小写, 默认是区分大小写的
:set noic(noignorecase的缩写) 恢复大小写敏感

/\Cword 区分大小写的查找
/word (不加 \C 默认就是区分大小写的)

/\cword 不区分大小写的查找



>> 行内查找

fx 查找本行光标后面的 x 字符, 使用分号查找下一个
3fa 在当前行查找第三个出现的a。
Fx 反向查找
t, 到逗号前的第一个字符。逗号可以变成其它字符。
常常这样使用 dt" 删除到 " 处
T, 反向查找





>> 选择/复制/删除/粘贴: (复制/剪切的内容可以跨文件)

x 删除当前光标下的字符
d 删除
dd 删除一行 2dd 向下剪切 2 行
dw 删除光标之后的单词剩余部分(包括单词后的空格)。
daw delete around word 删除光标所在的 整个单词, 包括单词后的空格
diw delete inner Word 删除光标所在单词, 不包括空格
d$ 删除光标之后的该行剩余部分。
dit 删除 标签内部内容
di" 删除 quotes 内部
dip 删除段落
das 删除整句


yy 即 yank. 复制整行,
p 在光标下一行粘贴
v 进入 选择模式选中多行, 再按下 y 复制, 或者 d 剪切
V 选中当前行
ctrl + v 垂直选择 列选择 (块操作)
:1,.d 删除第一行到当前行 ("."当前行 ,"1,."表示从第一行到当前行 ,"d"删除)

一旦选择, 可以做许多操作:

J → 把所有的行连接起来(变成一行)
< 或 > → 左右缩进
= → 自动给缩进 (陈皓注:这个功能相当强大,我太喜欢了)


>> 存储到缓冲区

1-9个数字缓冲区,用来保存最近删除的数据
a-z缓冲区用来保存保存用户需要临时保存的数据
如果用大写字母表示缓冲区,则数据被追加到后面

例如:
(寄存器操作都是双引号开头)

"ayy 把数据复制到a缓冲区里面 (前面要加上“)
"2p 把2号缓冲区的内容复制出来
"ap 把a缓冲区的内容复制出来


匿名寄存器 ""
编号寄存器 "0 到 "9
小删除寄存器 "-
26个命名寄存器 "a 到 "z
3个只读寄存器 ":, "., "%
Buffer交替文件寄存器 "#
表达式寄存器 "=
选区和拖放寄存器 "*, "+, "~
黑洞寄存器 "_ 所有删除或拷贝到黑洞寄存器的文本将会消失, 实现用x命令删除一个字符而不是剪切,具体命令是:"_x
搜索模式寄存器 "/



>> 修改 (删除后进入插入模式)

c 即 change. 功能和d相同,区别在于完成删除操作后进入INSERT MODE
cc 删除当前行,然后进入INSERT MODE, 等同 S
ciw change inner word 删除当前单词进入插入模式
ci" change inner " 删除双引号内的字符, 等待插入
ct) change to ) 删除到右括号(不包括右括号), 等待插入
gU (变大写)
gu (变小写)



>> 插入

i:在光标所在字符前开始插入
a:在光标所在字符后开始插入
o:在光标所在行的下面另起一新行插入
s:删除光标所在的字符并开始插入

I:在光标所在行的行首开始插入 如果行首有空格则在空格之后插入
A:在光标所在你行的行尾开始插入
O:在光标所在行的上面另起一行开始插入
S:删除光标所在行并开始插入





>> 撤销/恢复

u 撤销上一步的操作
Ctrl+r 恢复上一步被撤销的操作




>> 重做

重复操作: https://blog.csdn.net/ii1245712564/article/details/46496347
https://learnku.com/articles/21986
https://www.jianshu.com/p/05ffb5823d96

重复类型 重复操作符 回退操作符
文本改变重复 . u
行内查找重复 ; ,
全文查找重复 n N
文本替换重复 & u
宏重复 @[寄存器] u

>> 宏

qa 开始录制宏, 宏名为 a
q 停止录制
@a 执行宏 a
@@ 重复执行上次 replay 的宏

在一个只有一行且这一行只有“1”的文本中
qaYp<C-a>q
qa 开始录制
Yp 复制行.
<C-a> 增加1.
q 停止录制.
@a → 在1下面写下 2
@@ → 在2 正面写下3
100@@ 会创建新的100行,并把数据增加到 103.





>> 行号

:set nu 显示行号 nu === number
:set nonu 取消显示行号 nonu === nonumber

vim xxx +13 # 打开 xxx 光标定位到 13 行





>> 执行命令 并读取结果

:read !ls *pdf






>> 缓冲区

vim file1 file2 file3... 一次打开多个文件, 每个文件对应一个缓冲区, 第一个文件会被现实到窗口

:badd [+99] filename 新增一个缓冲区加载指定文件 (可指定光标行数)

:buffers, :files, :ls 列出所有缓冲区 (第一个数字是缓冲区的标号;第二个标记是缓冲区当前的状态;紧接着是与缓冲区所关联的文件名)
状态包括:
1. - 或 = 只读
1. a 激活, 当前正显示在窗口
1. h 隐藏, 已经载入, 但是没有显示在窗口
1. + 已经更改过的缓冲区
1. x 读取时报错
1. % 当前缓冲区(current buffer)
1. # alternate buffer 交换缓冲区
1. u unlisted buffer 只有 :ls! 才显示的 buffer

:buffer number/filename 显示缓冲区

:sbuffer number/filename 分割当前窗口开始编辑一个缓冲区

切换缓冲区 (根据默认设置,在切换到另一缓冲区之前,Vim将提示你保存当前缓冲区。你也可以使用:set hidden命令,允许在未保存的情况下切换缓冲区)

:bnext到下一个缓冲区;
:bprevious, :bNext到前一个缓冲区;
:blast到最后一个缓冲区;
:bfirst到第一个缓冲区


:bdelete filename,:bdelete 3或:3 bdelete命令来删除一个缓冲区 有改动, 则无法删除,
:bdelete! filename 强制删除
:1,3 bdelete命令来删除指定范围的缓冲区


:bunload filename 卸载 buffer, 类似 :bdelete, 但是 文件名仍然留存在 buffer list 中



>> 分屏 (:split 和 vsplit.)


分屏启动文件
vim -On file1 file2 ... (垂直分屏, n 是数字,表示分成几个屏)
vim -on file1 file2 ... (水平分屏)


Ctrl+W c 关闭当前分屏
Ctrl+W q 关闭当前分屏, 如果只剩最后一个了,则退出Vim

Ctrl+W s 上下分割当前打开的文件。
Ctrl+W v 左右分割当前打开的文件。


:sp filename 上下分割,并打开一个新的文件。
:vsp filename 左右分割,并打开一个新的文件。


Ctrl+W k/j/h/l 在多个分屏中切换光标 (若是方向键大写, 则是移动分屏)
Ctrl+W w 把光标移到下一个的屏中

Ctrl+W = 让所有的屏都有一样的高度
Ctrl+W +/- 增加/减少高度 (对于宽度你可以使用[Ctrl+W <]或是[Ctrl+W >])


:split → 创建分屏 (:vsplit创建垂直分屏)
<C-w><dir> : dir就是方向,可以是 hjkl 或是 ←↓↑→ 中的一个,其用来切换分屏。
<C-w>_ (或 <C-w>|) : 最大化尺寸 (<C-w>| 垂直分屏)
<C-w>+ (或 <C-w>-) : 增加尺寸