关于bash:自用RAC配置脚本

#!/bin/bash## Linux7上装置Oracle数据库一键配置脚本# 装置介质所在目录# You must put the installation package in the /tmp/soft directory, Like this:# /tmp/soft/# ├── 11# ├── 12# ├── 19# │ ├── LINUX.X64_193000_db_home.zip# │ ├── LINUX.X64_193000_grid_home.zip# │ ├── p34773504_190000_Linux-x86-64_OJVM+GI_RU_19.18.0.0.230117.zip# │ ├── p6880880_190000_Linux-x86-64-OPatch_12.2.0.1.36.zip# │ └── patch# │ ├── 34762026# │ ├── 34786990# │ ├── PatchSearch.xml# │ └── README.html# └── compat-libstdc++-33-3.2.3-72.el7.x86_64.rpm# 定义变量# db_type | si/SI --单实例;rac/RAC --RAC(两节点)export db_type=RAC# 定义 Public IP、Virtual IP、SCAN IP 等业务网段 IP 地址信息export PubIP1=192.168.11.31export hostname1=Jeff-Test-RAC01export PubIP2=192.168.11.32export hostname2=Jeff-Test-RAC02export VIP1=192.168.11.33export VIP1NAME=Jeff-Test-VIP1export VIP2=192.168.11.34export VIP2NAME=Jeff-Test-VIP2export SCANIP=192.168.11.35export SCANNAME=Jeff-Test-SCAN# Pub_Mask 业务网络子网掩码,数字 23 或者 255.255.254.0 均反对export Pub_Mask=23export scanPort=1525export cluster_name=Jeff-Testexport ORA_SID_PREFIX=Jeff0export DB_NAME=Jeffexport DB_UNIQUE_NAME=JeffTestexport ORA_SID1=Jeff01export ORA_SID2=Jeff02# 定义心跳网络 Private IP 信息export PrivIP1=11.11.11.31export Priv1NAME=Jeff-Test-Priv1export PrivIP2=11.11.11.32export Priv2NAME=Jeff-Test-Priv2# Priv_Mask 心跳网络子网掩码,数字 23 或者 255.255.254.0 均反对export Priv_Mask=24# ntpserver 用于配置 chronyd 服务export ntpserver=11.11.11.1# oraver:装置的数据库版本:11、12、18、19、23export oraver=19# 指定root明码、oracle&grid明码、DB治理明码export rootpw="Test01"export ORAPWD="Oracle_19C" # oracle && grid 设置雷同明码export db_admin_pwd="Oracle_19C" # 预设数据库治理应用的对立明码# needcdb 指定是否Y(y)须要 CDB 容器数据库,needpdb 是否Y(y)须要创立 PDB(限度创立 1 个),PDB_SID 指定 PDB 名字export needcdb=Yexport needpdb=yexport PDB_SID=Jeff_PDB# 指定内存、FRA参数设置,如下是很小的参数了,再小会导致装置失败export sgasize=1800MBexport sga_max_size=2000MBexport pgasize=600MBexport recovery_size=18000MBexport processes=5000# 软件装置目录设置export rootdir=/u01export ORA_BASE=$rootdir/app/oracleexport ORA_HOME=$ORA_BASE/product/19.3.0/db_1export ORA_INV=$rootdir/app/oraInventoryexport GI_BASE=$rootdir/app/gridexport GI_HOME=$rootdir/app/19.3.0/gridexport logfile=/tmp/install_$(date +%F).log# ASM 磁盘组调配# mapper_dir 指定提供给ASM治理的磁盘辨认门路,或者 /dev/mapper 等export mapper_dir=/dev# sd : 提供给ASM治理的磁盘前缀export mapper_prefix=sd# asm_ocr_disks: OCR\Voting 磁盘组,多组应用空格\,\;等字符隔开,倡议命令行应用,,不容易凌乱;不写前缀export asm_ocr_disks="b c d"export asm_ocr_dir=OCR# asm_fra_disks:归档日志、闪回区 磁盘组export asm_fra_disks="e f g"export asm_fra_dir=FRA# asm_data_disks:数据文件 磁盘组;默认 redo 寄存 $asm_data_dir 和 $asm_fra_dir,须要另外配置则调整脚本export asm_data_disks="h"export asm_data_dir=DATA# 指定是否须要格式化磁盘 Y(y)格式化,其余不执行export need_format=y# 装置介质指定export softroot=/tmp/softexport softdir=$softroot/$oraverexport db_zip_file=LINUX.X64_193000_db_home.zipexport grid_zip_file=LINUX.X64_193000_grid_home.zipexport opatch_zip_file=p6880880_190000_Linux-x86-64-OPatch_12.2.0.1.36.zipexport ru=34762026############################################ 以上是主要参数定义,以下局部可不设置 ############################################# 以下局部没有参数设置split1() { echo -e "\033[1;40;33m\n\n ===================================== $(date +'%F %T') =====================================\n$1\033[0m"}result_err() { echo -e "\033[5;31m\n (*>﹏<*)(*>﹏<*)(*>﹏<*) $1 (*>﹏<*)(*>﹏<*)(*>﹏<*)\n\033[0m"}result_ok() { echo -e "\n\033[32m (*^_^*)(*^_^*)(*^_^*) $1 (*^_^*)(*^_^*)(*^_^*)\033[0m\n"}# db_zip_file 默认值 LINUX.X64_193000_db_home.zipif [ -z ${db_zip_file} ];then db_zip_file=LINUX.X64_193000_db_home.zipfi# grid_zip_file 默认值 LINUX.X64_193000_grid_home.zipif [ -z ${grid_zip_file} ];then grid_zip_file=LINUX.X64_193000_grid_home.zipfi# opatch_zip_file 默认值 p6880880_190000_Linux-x86-64.zip,每次下载最新文件if [ -z ${opatch_zip_file} ];then opatch_zip_file=p6880880_190000_Linux-x86-64.zipfi# 须要apply的RU,不 apply 则置空export ru_zip_file=$softdir/p${ru}*.zip# 辨认 RU 对应的 zip 文件export RUs=$softdir/patch/$ru# 装置时须要同时装置的 RU 绝对路径if [ -d $softroot ] && [ ! -d $softdir/patch ];then mkdir -p $softdir/patchfiecho "################ Begin : $(date +'%F %T') ################">$logfilechmod -R 777 $softrootexport db_install_file=$softdir/$db_zip_fileexport grid_install_file=$softdir/$grid_zip_fileexport opatch_install_file=$softdir/$opatch_zip_fileexport upper_db_type=$(echo $db_type | tr [:lower:] [:upper:])if [ ! -f ${db_install_file} ];then result_err "Error: DB install file not found!" >>$logfile exit 1 elif [ ${upper_db_type} == "RAC" ] && [ ! -f ${grid_install_file} ];then result_err "You select Db type as RAC, But can not found GRID install file!">>$logfile exit 1 elif [ -n ${ru} ] && [ ! -f ${ru_zip_file} ];then result_err "You Want to apply an RU, But not found zip file!">>$logfile exit 1 if [ ! -f ${opatch_install_file} ];then result_err "You choose to apply RU and must update OPatch">>$logfile exit 1 fifisplit1 "# 依照输出参数解析各文件压缩包名如下,The input installation media is analyzed as follows:DB install file: $db_install_fileGRID install file: $grid_install_fileOPatch install file: $opatch_install_fileRU zip file: $ru_zip_file">>$logfilesplit1 "# 依照输出参数解析各文件压缩包名如下,The input installation media is analyzed as follows:DB install file: $db_install_fileGRID install file: $grid_install_fileOPatch install file: $opatch_install_fileRU zip file: $ru_zip_file"sleep 5# 如下实现掩码数字计算:255.255.254.0 转换 23export tmp1=$(echo $Pub_Mask | grep -o '\.' | wc -l)if [ $tmp1 -eq 3 ]; then export Pub_Mask=$(echo $Pub_Mask | sed 's/\./ + /g;s/255/8/g;s/254/7/g;s/252/6/g;s/248/5/g;s/240/4/g;s/224/3/g;s/192/2/g;s/128/1/g' | bc)elif [ $tmp1 -gt 0 ]; then result_err "# 请输出正确的 Public 网卡子网掩码($Pub_Mask)">>$logfile exit 1elif [ $Pub_Mask -lt 32 ] && [ $Pub_Mask -gt 1 ] && [ $tmp1 -eq 0 ]; then echo -eelse result_err "# 请输出正确的 Public($Pub_Mask) 网卡掩码位数字 2-31">>$logfile exit 1fiecho "# SCAN_IP: $SCANIP/$Pub_Mask" >>$logfileexport tmp2=$(echo $Priv_Mask | grep -o '\.' | wc -l)if [ $tmp2 -eq 3 ]; then export Priv_Mask=$(echo $Priv_Mask | sed 's/\./ + /g;s/255/8/g;s/254/7/g;s/252/6/g;s/248/5/g;s/240/4/g;s/224/3/g;s/192/2/g;s/128/1/g' | bc)elif [ $tmp2 -gt 0 ]; then result_err "# 请输出正确的 Private 网卡子网掩码($Priv_Mask)">>$logfile exit 1elif [ $Priv_Mask -lt 32 ] && [ $Priv_Mask -gt 1 ] && [ $tmp2 -eq 0 ]; then echo -eelse result_err "# 请输出正确的 Private($Priv_Mask) 网卡掩码位数字 2-31">>$logfile exit 1fiecho "# PrivIP1: $PrivIP1/$Priv_Mask" >>$logfile# 获取子网函数get_ipgate() { #ip地址转换整数 ipgate_int=$(echo $1 | awk -F'.' '{print $1*(256^3) + $2*(256^2) + $3*256 + $4}') #主机位:32 - $2 #按位运算,右移,ip地址移除主机位,保留网络位 #按位运算,左移,ip地址以0补全主机位 ((ipgate = ((ipgate_int >> ((32 - $2)))) << ((32 - $2)))) #获取子网:整数转换ip地址,整数右移取低8位 echo $((ipgate >> 24 & 0xFF)).$((ipgate >> 16 & 0xFF)).$((ipgate >> 8 & 0xFF)).$((ipgate & 0xFF))}export scan_subnet=$(get_ipgate $SCANIP $Pub_Mask)export priv_subnet=$(get_ipgate $PrivIP1 $Priv_Mask)echo "# pub-subnet: $scan_subnet ; Priv-subnet: $priv_subnet">>$logfilenetcard_name() { findIP=$1 # 依照网卡名行,结尾为数字编号,进行过滤抉择 for devEnum in $(ip link show | grep ^[0-9]\\+: | awk -F ' ' {'print $2'}); do # 去掉后缀冒号 devName=${devEnum%:} # 去掉可能的子网卡接口后缀 devName=${devName%@*} if [ $(ip addr show $devName | grep -c $findIP) -gt 0 ]; then echo $devName break fi done}if [ "$(hostname)" == "$hostname1" ]; then pub_netcard=$(netcard_name $PubIP1) priv_netcard=$(netcard_name $PrivIP1) echo "# pub_netcard: $pub_netcard ; priv_netcard: $priv_netcard" >>$logfilefi# 提取 ASM 磁盘信息,转换后提供 for 循环应用(-->21 asm 配置)export asm_ocr_disks=$(echo $asm_ocr_disks | sed 's/,/ /g' | sed 's/\./ /g' | sed 's/;/ /g' | sed 's/\// /g' | sed 's/\\/ /g')export asm_fra_disks=$(echo $asm_fra_disks | sed 's/,/ /g' | sed 's/\./ /g' | sed 's/;/ /g' | sed 's/\// /g' | sed 's/\\/ /g')export asm_data_disks=$(echo $asm_data_disks | sed 's/,/ /g' | sed 's/\./ /g' | sed 's/;/ /g' | sed 's/\// /g' | sed 's/\\/ /g')# 确认长期安装文件存储目录 /tmp 存在,且可用空间大于 10GBecho "# $(hostname) 查看 /tmp 目录存在,且可用空间大于 10GB:" >>$logfileif [ ! -d /tmp ]; then echo "# /tmp Filesystem does not exist,Create it." >>$logfile mkdir /tmpfiexport tmpsizemb=$(cd /tmp && df -PmT . | grep -v Filesystem | awk '{print $5}')if [ $tmpsizemb -lt 10240 ]; then cd /tmp && df -PmT . result_err "# /tmp Filesystem has size $tmpsizemb MB, less than 10G, exit this scripts." result_err "# /tmp Filesystem has size $tmpsizemb MB, less than 10G, exit this scripts." >>$logfile exit 1fi# 0. yum 查看及装置依赖包split1 "# 0. 查看 yum 装置源是否可用,并装置依赖包($(hostname)):"if [ $(hostname) == "$hostname1" ]; then split1 "# 0. 查看 yum 装置源是否可用,并装置依赖包($(hostname)):" >>$logfile echo '#!/bin/bash' >/tmp/yumlist.sh cat >>/tmp/yumlist.sh <<yumlistexport yumlist=\$(yum list 2>/dev/null | wc -l)if [ \$yumlist -gt 2000 ]; then export yumusable=1 echo "# yum avaliable on \$(hostname)"else export yumusable=0 echo "# YUM Unavailable on \$(hostname), Try to mount /dev/cdrom make it available" if [ -h /dev/cdrom ];then echo "# /dev/cdrom exist, mount it to /mnt for use" mount /dev/cdrom /mnt/ if [ ! -d /etc/yum.repos.d/bak ]; then mkdir /etc/yum.repos.d/bak fi mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak/ echo "[Local_mnt]name=Local_mntbaseurl=file:///mntenabled=1gpgcheck=0" >/etc/yum.repos.d/Local_mnt.repo yum -y clean all yum makecache else echo "# /dev/cdrom does not exist, do nothing." fi if [ \$(yum list 2>/dev/null | wc -l) -lt 2000 ];then echo "# Yum is not available on \$(hostname), try Failed" exit 1 fifi# Install need rpmsecho "# Installing need rpms on \$(hostname) ... ..."sleep 5yum install -y bc binutils compat-libcap1 compat-libstdc++-33 elfutils-libelf elfutils-libelf-devel fontconfig-devel glibc glibc-devel ksh libaio libaio-devel libXrender libXrender-devel libX11 libXau libXi libXtst libgcc libstdc++ libstdc++-devel libxcb make policycoreutils policycoreutils-python smartmontools sysstat net-tools nfs-utils python python-configshell python-rtslib python-six targetcli xorg-x11-xauth xorg-x11-fonts-* xorg-x11-font-utils xorg-x11-fonts-Type1 xorg-x11-apps xclock ncurses-devel redhat-lsb-core readline-devel vim-enhanced bzip2 chrony iotop xterm lsof tree expect gcc-c++ zip unzip 2>/dev/nullyumlist chmod 777 /tmp/yumlist.sh sh /tmp/yumlist.sh >>$logfile if [ $? -ne 0 ]; then result_err "# $PubIP1 执行 yumlist.sh, Failed" result_err "# $PubIP1 执行 yumlist.sh, Failed" >>$logfile cat /tmp/yumlist.sh >>$logfile exit 1 else result_ok "# $PubIP1 执行 yumlist.sh, successful" result_ok "# $PubIP1 执行 yumlist.sh, successful" >>$logfile fi expect >>$logfile <<EOFset timeout 100spawn ssh -o StrictHostKeyChecking=no $PubIP2expect { "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$rootpw\r"}}expect "root@*" {send "if \[ ! -d /tmp \]; then mkdir /tmp; fi\r"}expect "root@*" {send "exit\r"}expect eofEOF # 近程 $hostname2 做雷同脚本 # 拷贝脚本到对端 echo "# 近程 $hostname2 执行雷同 yumlist 脚本" echo "# 近程 $hostname2 执行雷同 yumlist 脚本" >>$logfile if [ $(grep $PubIP2 ~/.ssh/authorized_keys 2>/dev/null | wc -l) -lt 1 ];then expect >>$logfile <<EOFset timeout 100spawn scp /tmp/yumlist.sh $PubIP2:/tmp/expect { "password:" {send "$rootpw\r"}}expect eofEOF else scp /tmp/yumlist.sh $PubIP2:/tmp/ fi if [ $? -ne 0 ]; then result_err "# scp yumlist.sh 到 $PubIP2, Failed" result_err "# scp yumlist.sh 到 $PubIP2, Failed" >>$logfile exit 1 else result_ok "# scp yumlist.sh 到 $PubIP2, successful" result_ok "# scp yumlist.sh 到 $PubIP2, successful" >>$logfile fi # 执行脚本 expect >>$logfile <<EOFset timeout 100spawn ssh $PubIP2expect { "password:" {send "$rootpw\r"}}expect "root@*" {send "sh /tmp/yumlist.sh\r"}expect "root@*" {send "exit\r"}expect eofEOF if [ $? -ne 0 ]; then result_err "# $PubIP2 执行 yumlist.sh, Failed" result_err "# $PubIP2 执行 yumlist.sh, Failed" >>$logfile exit 1 else result_ok "# $PubIP2 执行 yumlist.sh, successful" result_ok "# $PubIP2 执行 yumlist.sh, successful" >>$logfile fielse result_err "# Please run this script on $hostname1 node!" result_err "# Please run this script on $hostname1 node!" >>$logfile exit 1fi# 1. 配置hostssplit1 "# 1. 配置 $(hostname) /etc/hosts 文件:"split1 "# 1. 配置 $(hostname) /etc/hosts 文件:" >>$logfilehostnamectl set-hostname $hostname1 --staticcp /etc/hosts /etc/hosts_$(date +"%Y%m%d_%H%M%S")sed -i "/IP\|^$\|$PubIP1\|$PubIP2\|$PrivIP1\|$PrivIP2\|$VIP1\|$VIP2\|$SCANIP/d" /etc/hostsecho "# Public IP$PubIP1 $hostname1$PubIP2 $hostname2# Private IP$PrivIP1 $Priv1NAME$PrivIP2 $Priv2NAME# Virtual IP (VIP)$VIP1 $VIP1NAME$VIP2 $VIP2NAME# SCAN IP$SCANIP $SCANNAME" >>/etc/hostscat /etc/hosts >>$logfile# 拷贝脚本到对端echo "# 拷贝 /etc/hosts 文件到 $hostname2 :" echo "# 拷贝 /etc/hosts 文件到 $hostname2 :" >>$logfileexpect >>$logfile<< EOFset timeout 100spawn scp /etc/hosts $PubIP2:/etc/expect { "password:" {send "$rootpw\r"}}expect eofEOF# 执行脚本expect >>$logfile << EOFset timeout 100spawn ssh $PubIP2expect { "password:" {send "$rootpw\r"}}expect "root@*" {send "hostnamectl set-hostname $hostname2 --static\r"}expect "root@*" {send "exit\r"}expect eofEOF# 2. 配置 OS 组及用户# https://docs.oracle.com/en/database/oracle/oracle-database/19/cwsol/example-of-creating-minimal-groups-users-paths.html#GUID-103186A1-74E0-42A8-AC3D-15AF833DCB40split1 "# 2. 配置 OS 组及用户 on $(hostname) :"split1 "# 2. 配置 OS 组及用户 on $(hostname) :" >>$logfileecho '#!/bin/bash' >/tmp/usergrp.shcat >>/tmp/usergrp.sh <<usergecho "# \$(hostname) 配置 OS 组及用户:"groupadd -g 54321 oinstallgroupadd -g 54322 dbagroupadd -g 54323 opergroupadd -g 54324 backupdbagroupadd -g 54325 dgdbagroupadd -g 54326 kmdbagroupadd -g 54327 asmdbagroupadd -g 54328 asmopergroupadd -g 54329 asmadmingroupadd -g 54330 racdbau1=\$(grep oracle /etc/passwd | wc -l)u2=\$(grep oracle /etc/passwd | wc -l)if [ \$u1 -ge 1 ]; then userdel oraclefiif [ \$u2 -ge 1 ]; then userdel gridfiuseradd -g oinstall -G dba,oper,backupdba,dgdba,kmdba,asmdba,asmoper,asmadmin,racdba -u 1000 -m oracleuseradd -g oinstall -G dba,asmdba,asmoper,asmadmin,racdba -u 1001 -m gridecho "$ORAPWD" | passwd --stdin oracleecho "$ORAPWD" | passwd --stdin gridmkdir -p $GI_HOMEmkdir -p $GI_BASEmkdir -p $ORA_BASEmkdir -p $ORA_HOMEmkdir -p $ORA_INVchown -R grid:oinstall $rootdirchown -R oracle:oinstall $ORA_BASEchmod -R 775 $rootdirusergchmod +x /tmp/usergrp.sh; sh /tmp/usergrp.sh >>$logfile# 拷贝脚本到 $hostname2 并执行echo "# 拷贝脚本 /tmp/usergrp.sh 到 $hostname2 并执行:"echo "# 拷贝脚本 /tmp/usergrp.sh 到 $hostname2 并执行:" >>$logfileexpect >>$logfile << EOFset timeout 100spawn scp /tmp/usergrp.sh $PubIP2:/tmp/expect { "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$rootpw\r"}}expect eofEOF# 执行脚本expect >>$logfile << EOFset timeout 100spawn ssh $PubIP2expect { "password:" {send "$rootpw\r"}}expect "root@*" {send "sh /tmp/usergrp.sh\r"}expect "root@*" {send "exit\r"}expect eofEOF# 3. 设置环境变量配置文件split1 "# 3. 设置环境变量配置文件 .bash_profile:"split1 "# 3. 设置环境变量配置文件 .bash_profile:" >>$logfileif [ "$(hostname)" == "$hostname1" ]; then # For root on node01 sed -i '/ORACLE_HOME/d' /root/.bash_profile echo "export ORACLE_HOME=$GI_HOMEexport GRID_HOME=\$ORACLE_HOMEexport PATH=\$PATH:\$ORACLE_HOME/bin:\$ORACLE_HOME/OPatch" >>/root/.bash_profile echo -e "# root: \n$(cat /root/.bash_profile)\n" >>$logfile # For root on node02 expect >>$logfile << EOFset timeout 100spawn scp /root/.bash_profile $PubIP2:/root/expect { "password:" {send "$rootpw\r"}}expect eofEOF # # For oracle on node01 sed -i '/export\|alias\|umask\|^$/d' /home/oracle/.bash_profile cat >>/home/oracle/.bash_profile <<EOFumask 022export TMP=/tmpexport TMPDIR=\$TMPexport ORACLE_HOSTNAME=$hostname1export ORACLE_BASE=$ORA_BASEexport ORACLE_HOME=$ORA_HOMEexport ORACLE_SID=$ORA_SID1export ORACLE_INVENTORY=$ORA_INVexport INVENTORY_LOCATION=\$ORACLE_INVENTORY# export ORACLE_PDB_SID=$PDB_SIDexport NLS_LANG="AMERICAN_AMERICA.AL32UTF8"export NLS_DATE_FORMAT="yyyy-mm-dd HH24:MI:SS"export TNS_ADMIN=\$ORACLE_HOME/network/adminexport LD_LIBRARY_PATH=\$ORACLE_HOME/lib:/lib:/usr/libexport PATH=.:\$PATH:\$HOME/bin:\$ORACLE_HOME/bin:\$ORACLE_HOME/OPatchexport THREADS_FLAG=native# alias sqlplus='rlwrap sqlplus'# alias rman='rlwrap rman'alias ss='sqlplus / as sysdba'EOF echo -e "# oracle: \n$(cat /home/oracle/.bash_profile)\n" >>$logfile # For oracle on node02 expect >>$logfile << EOFset timeout 100spawn scp /home/oracle/.bash_profile oracle@$PubIP2:/home/oracle/expect { "password:" {send "$ORAPWD\r"}}expect eofEOF# 执行脚本expect >>$logfile << EOFset timeout 100spawn ssh oracle@$PubIP2expect { "password:" {send "$ORAPWD\r"}}expect "oracle@*" {send "sed -i 's/$ORA_SID1/$ORA_SID2/g' /home/oracle/.bash_profile\r"}expect "oracle@*" {send "sed -i 's/$hostname1/$hostname2/g' /home/oracle/.bash_profile\r"}expect "oracle@*" {send "exit\r"}expect eofEOF # # For grid on node01 sed -i '/export\|alias\|umask\|^$/d' /home/grid/.bash_profile cat >>/home/grid/.bash_profile <<EOFumask 022export TMP=/tmpexport TMPDIR=\$TMPexport ORACLE_HOSTNAME=$hostname1export ORACLE_SID=+ASM1export ORACLE_BASE=$GI_BASEexport GRID_BASE=\$ORACLE_BASEexport ORACLE_HOME=$GI_HOMEexport GRID_HOME=\$ORACLE_HOMEexport ORACLE_INVENTORY=$ORA_INVexport INVENTORY_LOCATION=\$ORACLE_INVENTORYexport NLS_LANG="AMERICAN_AMERICA.AL32UTF8"export NLS_DATE_FORMAT="yyyy-mm-dd HH24:MI:SS"export TNS_ADMIN=\$ORACLE_HOME/network/adminexport LD_LIBRARY_PATH=\$ORACLE_HOME/lib:/lib:/usr/libexport PATH=.:\$PATH:\$HOME/bin:\$ORACLE_HOME/bin:\$ORACLE_HOME/OPatchexport THREADS_FLAG=native# alias sqlplus='rlwrap sqlplus'# alias rman='rlwrap rman'alias ss='sqlplus / as sysasm'EOF echo -e "# grid: \n$(cat /home/grid/.bash_profile)\n" >>$logfile # For grid on node02 expect >>$logfile << EOFset timeout 100spawn scp /home/grid/.bash_profile grid@$PubIP2:/home/grid/expect { "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$ORAPWD\r"}}expect eofEOF# 执行脚本expect >>$logfile << EOFset timeout 100spawn ssh grid@$PubIP2expect { "(yes/no)" {send "yes\r"; exp_continue} "password:" {send "$ORAPWD\r"}}expect "grid@*" {send "sed -i 's/ASM1/ASM2/g' /home/grid/.bash_profile\r"}expect "grid@*" {send "sed -i 's/$hostname1/$hostname2/g' /home/grid/.bash_profile\r"}expect "grid@*" {send "exit\r"}expect eofEOFelse result_err "# Please execute this script on $hostname1,while this machine hostname is: $(hostname)" result_err "# Please execute this script on $hostname1,while this machine hostname is: $(hostname)" >>$logfile exit 1fiif [ $? -ne 0 ]; then result_err "# 设置环境变量, Failed" result_err "# 设置环境变量, Failed" >>$logfile exit 1else result_ok "# 设置环境变量, successful" result_ok "# 设置环境变量, successful" >>$logfilefi# 4. Check OS and Configuration 查看系统配置# $ISVirtual ≥ 1, is Virtual Machine# export routeip=$(route -n | egrep -iv "Gateway|routing" | awk '{print $2}' | grep -v "0\.0\.0\.0")# export routeprefix=$(echo $routeip | awk -F"." '{print $1"."$2}')split1 "# 4. Check OS and Configuration 查看系统配置:"split1 "# 4. Check OS and Configuration 查看系统配置:" >>$logfileecho '#!/bin/bash'>/tmp/osinfo.shcat >>/tmp/osinfo.sh <<osinfoexport routeprefix=\$(route -n | egrep -iv "Gateway|routing" | awk '{print \$2}' | grep -v "0\.0\.0\.0" | awk -F"." '{print \$1"."\$2}')export client_ip=\$(ip a | grep -w inet | grep -Eiv "secondary|virbr|docker|:|127.0.0.1|192.168.1\." | awk -F " +|/" '{ print \$3}' | grep \$routeprefix)export ISVirtual=\$(dmidecode -s system-product-name | grep -i virtual | wc -l)export MachineName=\$(dmidecode -s system-product-name)if [ -f /etc/yum/pluginconf.d/subscription-manager.conf ]; then sed -i 's/enabled=1/enabled=0/g' /etc/yum/pluginconf.d/subscription-manager.conf yum remove subscription-manager -yelse echo "subscription-manager.conf file not exist, Do nothing"fiif [ \$ISVirtual -eq 0 ]; then machine_type="物理机" export RAM_GB=\$(dmidecode | grep -P -A5 "Memory\s+Device" | grep GB | awk '{sum+=\$2} END {print sum}')else machine_type="虚拟机" export RAM_GB=\$(free -g | grep Mem | awk '{print \$2}')ficpus=\$(lscpu | grep ^'CPU(s):' | awk '{print \$2}' | grep -v ^\$)sockets=\$(lscpu | grep ^'Socket(s):' | awk -F":" '{print \$2}' | grep -v ^\$ | sed 's/^[ ]*//g')cores_per_sock=\$(lscpu | grep "Core(s) per socket:" | awk -F":" '{print \$2}' | grep -v ^\$ | sed 's/^[ ]*//g')cpu_name=\$(lscpu | grep "Model name:" | awk -F":" '{print \$2}' | grep -v ^\$ | sed 's/^[ ]*//g')Cores=\$((\${sockets} * \${cores_per_sock}))echo "# \$(hostname) 硬件及OS Version信息采集: 服务器IP:\$client_ip 服务器类型:\$machine_type 机器型号/平台:\$MachineName CPU name:\$cpu_name CPU(s):\$cpus Core(s):\$Cores Socket(s):\$sockets Core(s) per socket:\$cores_per_sock"osinfochmod +x /tmp/osinfo.sh; sh /tmp/osinfo.sh >>$logfileif [ $? -ne 0 ]; then result_err "# Check OS and Configuration 查看系统配置, Failed" result_err "# Check OS and Configuration 查看系统配置, Failed" >>$logfile exit 1else result_ok "# Check OS and Configuration 查看系统配置, successful" result_ok "# Check OS and Configuration 查看系统配置, successful" >>$logfilefi# 拷贝到 $hostname2 并执行脚本expect >>$logfile << EOFset timeout 100spawn scp /tmp/osinfo.sh $PubIP2:/tmp/expect { "password:" {send "$rootpw\r"}}expect eofEOF# 执行脚本expect >>$logfile << EOFset timeout 100spawn ssh $PubIP2expect { "password:" {send "$rootpw\r"}}expect "root@*" {send "sh /tmp/osinfo.sh\r"}expect "root@*" {send "exit\r"}expect eofEOFif [ $? -ne 0 ]; then result_err "# $hostname2 查看系统配置, Failed" result_err "# $hostname2 查看系统配置, Failed" >>$logfile exit 1else result_ok "# $hostname2 查看系统配置, successful" result_ok "# $hostname2 查看系统配置, successful" >>$logfilefi# 5. 解压软件,如果存在,仅节点1执行split1 "# 5. 解压软件,如果存在,仅节点1执行:"split1 "# 5. 解压软件,如果存在,仅节点1执行:" >>$logfileif [ -f $grid_install_file ] && [ "$(hostname)" == "$hostname1" ]; then echo "# unziping grid install file to $GI_HOME..." echo "# unziping grid install file to $GI_HOME..." >>$logfile su - grid -c "unzip -qo $grid_install_file -d $GI_HOME/; mv $GI_HOME/OPatch $GI_HOME/OPatch.$(date +%Y%m%d%H%M); unzip -qo $opatch_install_file -d $GI_HOME/" >>$logfile echo -e "\n# unzip grid install file End!\n$(ls $GI_HOME/)\n" >>$logfilefiif [ -f $db_install_file ] && [ "$(hostname)" == "$hostname1" ]; then echo "# unziping DB install file to $ORA_HOME..." echo "# unziping DB install file to $ORA_HOME..." >>$logfile su - oracle -c "unzip -qo $db_install_file -d $ORA_HOME/; mv $ORA_HOME/OPatch $ORA_HOME/OPatch.$(date +%Y%m%d%H%M); unzip -qo $opatch_install_file -d $ORA_HOME/" >>$logfile echo -e "\n# unzip DB install file End!\n$(ls $ORA_HOME/)\n" >>$logfilefiif [ -f ${ru_zip_file} ] && [ "$(hostname)" == "$hostname1" ];then echo "# unziping RU zip file to $softdir/patch/..." echo "# unziping RU zip file to $softdir/patch/..." >>$logfile unzip -qo ${ru_zip_file} -d $softdir/patch/; chmod -R 777 $softdir/patch/ echo -e "\n# unzip Ru zip file End!\n$(ls -l $softdir/patch/)\n" >>$logfilefichown -R grid:oinstall $rootdirchown -R oracle:oinstall $ORA_BASEchmod -R 775 $rootdir# 测试环境空间不太够用,$rootdir 和 /tmp 都在/ 下,判断目录有余时清理zip文件tmpmnt=$(df -m /tmp/ | grep -v Filesystem | awk '{print $6}' | sed -e '/^$/d')rootdirmnt=$(df -m $rootdir | grep -v Filesystem | awk '{print $6}' | sed -e '/^$/d')rootdirleft=$(df -m $rootdir | grep -v Filesystem | awk '{print $4}' | sed -e '/^$/d')if [ "$tmpmnt" == "$rootdirmnt" ] && [ $rootdirleft -le 20480 ];then echo "# $rootdirmnt 残余空间太少,且同 zip 文件雷同文件系统,删除 zip 文件" echo "# $rootdirmnt 残余空间太少,且同 zip 文件雷同文件系统,删除 zip 文件" >>$logfile rm -rf $softdir/*.zip >>$logfileelif [ $rootdirleft -le 20480 ]; then echo "# $rootdirmnt 残余空间太少, 请留神" echo "# $rootdirmnt 残余空间太少, 请留神" >>$logfilefi# 6. 配置 ssh 互信split1 "# 6. 配置 ssh 互信(oracle grid root),仅在解压文件的节点1执行:"split1 "# 6. 配置 ssh 互信(oracle grid root),仅在解压文件的节点1执行:" >>$logfileif [ -f $GI_HOME/oui/prov/resources/scripts/sshUserSetup.sh ]; then for i in oracle grid root; do if [ "$i" == "root" ]; then export pass=$rootpw else export pass=$ORAPWD fi expect >>$logfile << EOFspawn $GI_HOME/oui/prov/resources/scripts/sshUserSetup.sh -user $i -hosts "$hostname1 $hostname2" -advanced -noPromptPassphraseexpect { "(yes/no)" {send "yes\r"; exp_continue} "*assword:" {send "$pass\r";exp_continue}}expect eofEOF expect >>$logfile << EOFspawn $GI_HOME/oui/prov/resources/scripts/sshUserSetup.sh -user $i -hosts "$Priv1NAME $Priv2NAME" -advanced -noPromptPassphraseexpect { "(yes/no)" {send "yes\r"; exp_continue} "*assword:" {send "$pass\r";exp_continue}}expect eofEOF for g in $hostname1 $hostname2 $Priv1NAME $Priv2NAME; do echo "# Testing $g for $i" >>$logfile if [ "$i" == "root" ]; then ssh -l $i -o StrictHostKeyChecking=no $g date >>$logfile else su - $i -c "ssh -l $i -o StrictHostKeyChecking=no $g date" >>$logfile fi if [ $? -ne 0 ]; then result_err "# $i Test $g sshUserSetup Maybe Failed" result_err "# $i Test $g sshUserSetup Maybe Failed" >>$logfile exit 1 else result_ok "# $i 配置 $g ssh 互信, successful" result_ok "# $i 配置 $g ssh 互信, successful" >>$logfile fi done donefi# 7. 查看 RAM\Swap\shm# swap 不须要设置过大,最大32GB,Oracle倡议是16GB(内存>=16GB);eq 内存大小(小于16GB)# shm 默认是内存的1/2,配置Oracle会不够用,调整为等同可用内存大小(自己未取总内存)split1 "# 7. 查看 RAM\Swap\shm"split1 "# 7. 查看 RAM\Swap\shm" >>$logfileecho '#!/bin/bash'>/tmp/shm.shcat >>/tmp/shm.sh<<shmecho "# 设置 /dev/shm:\$(hostname)"shmsizegb=\$(echo "scale=0;\$(df -PmT /dev/shm | grep shm | awk '{print \$3}')/1024" | bc)swapgb=\$(echo "scale=0;\$(grep SwapTotal /proc/meminfo | awk '{print \$2}')/1024/1024" | bc)memgb=\$(echo "scale=0;\$(grep MemTotal /proc/meminfo | awk '{print \$2}')/1024/1024" | bc)if [ \$shmsizegb -lt \$memgb ]; then echo "resize /dev/shm to \${memgb}G" sed -i '/\/dev\/shm/d' /etc/fstab echo "tmpfs /dev/shm tmpfs defaults,size=\${memgb}G,noatime,nodiratime 0 0" >>/etc/fstab mount -o remount /dev/shmelse echo "/dev/shm equal to MemTotal, Now \$(df -h | grep /dev/shm | awk '{print $2}')"fishmchmod +x /tmp/shm.sh;sh /tmp/shm.sh >>$logfileif [ $? -ne 0 ]; then result_err "# 查看 RAM\Swap\shm, Failed" result_err "# 查看 RAM\Swap\shm, Failed" >>$logfile exit 1else result_ok "# 查看 RAM\Swap\shm, successful" result_ok "# 查看 RAM\Swap\shm, successful" >>$logfilefiscp /tmp/shm.sh $hostname2:/tmp/ >>$logfileecho "sh /tmp/shm.sh;exit" | ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 查看 RAM\Swap\shm, Failed" result_err "# $hostname2 查看 RAM\Swap\shm, Failed" >>$logfile exit 1else result_ok "# $hostname2 查看 RAM\Swap\shm, successful" result_ok "# $hostname2 查看 RAM\Swap\shm, successful" >>$logfilefi# 8. 禁用防火墙firewalldsplit1 "# 8. $(hostname) 禁用防火墙 firewalld:" split1 "# 8. $(hostname) 禁用防火墙 firewalld:" >>$logfilesystemctl stop firewalldsystemctl disable firewalldecho "# $hostname2 禁用防火墙 firewalld" >>$logfileecho "systemctl stop firewalld;systemctl disable firewalld;exit" | ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 禁用防火墙 firewalld, Failed" result_err "# $hostname2 禁用防火墙 firewalld, Failed" >>$logfile exit 1else result_ok "# $hostname2 禁用防火墙 firewalld, successful" result_ok "# $hostname2 禁用防火墙 firewalld, successful" >>$logfilefi# 9. 禁用 SElinuxsplit1 "# 9. 禁用 SElinux:"split1 "# 9. 禁用 SElinux:" >>$logfileecho '#!/bin/bash' >/tmp/sel.shcat >>/tmp/sel.sh <<selecho "# 9 禁用 SElinux: \$(hostname)"if [ \$(getenforce) != "Disabled" ]; then export SELINUX=\$(grep ^SELINUX= /etc/selinux/config) if [ \$SELINUX != "SELINUX=disabled" ]; then cp /etc/selinux/config /etc/selinux/config_\$(date +"%Y%m%d_%H%M%S") && sed -i 's/SELINUX\=enforcing/SELINUX\=disabled/g' /etc/selinux/config echo "Changed SElinux configuration, reboot later..." else echo "The SELINUX configuration file has been modified before." fi setenforce 0else echo "SELINUX is already disabled, Do nothing."fiselchmod 777 /tmp/sel.sh;sh /tmp/sel.sh >>$logfileif [ $? -ne 0 ]; then result_err "# 禁用 SElinux, Failed" result_err "# 禁用 SElinux, Failed" >>$logfile exit 1else result_ok "# 禁用 SElinux, successful" result_ok "# 禁用 SElinux, successful" >>$logfilefiscp /tmp/sel.sh $hostname2:/tmp/ >>$logfileecho "sh /tmp/sel.sh;exit" | ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 禁用 SElinux, Failed" result_err "# $hostname2 禁用 SElinux, Failed" >>$logfile exit 1else result_ok "# $hostname2 禁用 SElinux, successful" result_ok "# $hostname2 禁用 SElinux, successful" >>$logfilefi# 10. 禁用虚构网卡split1 "# 10. 禁用虚构网卡 libvirtd on $hostname1:"split1 "# 10. 禁用虚构网卡 libvirtd on $hostname1:" >>$logfilesystemctl stop libvirtdsystemctl disable libvirtdyum remove libvirt-libsecho "# 10 禁用虚构网卡,for $hostname2"echo "# 10 禁用虚构网卡,for $hostname2" >>$logfileecho "systemctl stop libvirtd;systemctl disable libvirtd;yum remove libvirt-libs;exit"|ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 禁用虚构网卡, Failed" result_err "# $hostname2 禁用虚构网卡, Failed" >>$logfileelse result_ok "# $hostname2 禁用虚构网卡, successful" result_ok "# $hostname2 禁用虚构网卡, successful" >>$logfilefi# 11. 禁用 zeroconf 路由# Zero configuration networking(zeroconf)零配置网络服务标准,是一种用于主动生成可用IP地址的网络技术,不须要额定的手动配置和专属的配置服务器。Avahi 是Zeroconf标准的开源实现split1 "# 11. 禁用 zeroconf 路由:"split1 "# 11. 禁用 zeroconf 路由:" >>$logfile# 配置 NOZEROCONFsed -i '/NOZEROCONF/d' /etc/sysconfig/networkecho "NOZEROCONF=yes" >>/etc/sysconfig/network# 禁用 Avahisystemctl stop avahi-daemon.servicesystemctl stop avahi-daemon.socketsystemctl disable avahi-daemon.servicesystemctl disable avahi-daemon.socketecho "# 禁用 zeroconf 路由 for $hostname2:"echo "# 禁用 zeroconf 路由 for $hostname2:" >>$logfilescp /etc/sysconfig/network $hostname2:/etc/sysconfig/ >>$logfileecho "systemctl stop avahi-daemon.service;systemctl stop avahi-daemon.socket;systemctl disable avahi-daemon.service;systemctl disable avahi-daemon.socket;exit"|ssh $hostname2 >>$logfile# 12. 禁用通明大页 THP 和 NUMAsplit1 "# 12. 禁用通明大页 THP 和 NUMA:"split1 "# 12. 禁用通明大页 THP 和 NUMA:" >>$logfileecho '#!/bin/bash'>/tmp/thp.shcat >>/tmp/thp.sh<<thpecho "# 12 \$(hostname) 禁用通明大页 THP 和 NUMA:"export AnonHP=\$(grep AnonHugePages /proc/meminfo | awk '{print \$2}')export TPHstatus=\$(cat /sys/kernel/mm/transparent_hugepage/enabled | awk -F"[][]" '{print \$2}')if [ \$AnonHP -gt 0 ] || [ \$TPHstatus != "never" ]; then echo "Checked TPH is not completely disabled!" cp /etc/default/grub /etc/default/grub_\$(date +"%Y%m%d_%H%M%S") && sed -i 's/quiet"/quiet numa=off transparent_hugepage=never"/' /etc/default/grub if [ -d /sys/firmware/efi ]; then # On UEFI: echo "OS running use UEFI, Turn off TPH By grub /boot/efi/EFI/redhat/grub.cfg" cp /boot/efi/EFI/redhat/grub.cfg /boot/efi/EFI/redhat/grub.cfg_\$(date +"%Y%m%d_%H%M%S") && grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg else # On BIOS: echo "OS running use BIOS, Turn off TPH By grub /boot/grub2/grub.cfg" cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg_\$(date +"%Y%m%d_%H%M%S") && grub2-mkconfig -o /boot/grub2/grub.cfg fielse echo "TPH already disabled."fised -i '/transparent_hugepage/d' /etc/rc.localsed -i "/^fi\b/Id" /etc/rc.localecho "if test -f /sys/kernel/mm/transparent_hugepage/enabled; then echo never > /sys/kernel/mm/transparent_hugepage/enabledfiif test -f /sys/kernel/mm/transparent_hugepage/defrag; then echo never > /sys/kernel/mm/transparent_hugepage/defragfi" >>/etc/rc.localthpchmod +x /tmp/thp.sh;sh /tmp/thp.sh >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname1 禁用通明大页 THP 和 NUMA, Failed" result_err "# $hostname1 禁用通明大页 THP 和 NUMA, Failed" >>$logfile exit 1else result_ok "# $hostname1 禁用通明大页 THP 和 NUMA, successful" result_ok "# $hostname1 禁用通明大页 THP 和 NUMA, successful" >>$logfilefiscp /tmp/thp.sh $hostname2:/tmp/echo "sh /tmp/thp.sh;exit" | ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 禁用通明大页 THP 和 NUMA, Failed" result_err "# $hostname2 禁用通明大页 THP 和 NUMA, Failed" >>$logfile exit 1else result_ok "# $hostname2 禁用通明大页 THP 和 NUMA, successful" result_ok "# $hostname2 禁用通明大页 THP 和 NUMA, successful" >>$logfilefi# 13. 配置时钟同步机制 chronydsplit1 "# 13. 配置 chronyd 时钟同步机制 chronyd:"split1 "# 13. 配置 chronyd 时钟同步机制 chronyd:" >>$logfilesystemctl stop ntpdsystemctl disable ntpdsed -i "/$ntpserver\|logchange\|syslog/Id"sed -i "/server 0/i server $ntpserver iburst" /etc/chrony.confsed -i "/pool.ntp.org/d" /etc/chrony.confsed -i "/makestep/d" /etc/chrony.confecho "# Send message to syslog when clock adjustment is larger than 0.1 seconds.logchange 0.1" >>/etc/chrony.confsystemctl restart chronydsystemctl enable chronydscp /etc/chrony.conf $hostname2:/etc/ >>$logfileecho "systemctl stop ntpd;systemctl disable ntpd;systemctl restart chronyd;systemctl enable chronyd;exit"|ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 配置 chronyd 时钟同步机制, Failed" result_err "# $hostname2 配置 chronyd 时钟同步机制, Failed" >>$logfile exit 1else result_ok "# $hostname2 配置 chronyd 时钟同步机制, successful" result_ok "# $hostname2 配置 chronyd 时钟同步机制, successful" >>$logfilefi# 14. 批改登录验证split1 "# 14. 批改 pam 登录验证:"split1 "# 14. 批改 pam 登录验证:" >>$logfilesed -i '/pam_limits.so/d' /etc/pam.d/loginecho "session required pam_limits.sosession required /lib64/security/pam_limits.so" >>/etc/pam.d/loginecho "# 14 批改 pam 登录验证:for $hostname2" >>$logfilescp /etc/pam.d/login $hostname2:/etc/pam.d/ >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 批改 pam 登录验证, Failed" result_err "# $hostname2 批改 pam 登录验证, Failed" >>$logfile exit 1else result_ok "# $hostname2 批改 pam 登录验证, successful" result_ok "# $hostname2 批改 pam 登录验证, successful" >>$logfilefi# 15. 批改 limit 资源限度split1 "# 15. 批改 /etc/security/limits.conf limit 资源限度:"split1 "# 15. 批改 /etc/security/limits.conf limit 资源限度:" >>$logfilesed -i "/oracle\|grid\|oinstall/d" /etc/security/limits.confecho "# Add oinstall group limits@oinstall soft nofile 16384@oinstall hard nofile 65536@oinstall soft nproc 16384@oinstall hard nproc 65536@oinstall soft stack 16384@oinstall hard stack 32768@oinstall soft memlock unlimited@oinstall hard memlock unlimited" >>/etc/security/limits.confecho "# 批改 /etc/security/limits.conf limit 资源限度: for $hostname2"echo "# 批改 /etc/security/limits.conf limit 资源限度: for $hostname2" >>$logfilescp /etc/security/limits.conf $hostname2:/etc/security/ >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 批改 /etc/security/limits.conf limit 资源限度, Failed" result_err "# $hostname2 批改 /etc/security/limits.conf limit 资源限度, Failed" >>$logfile exit 1else result_ok "# $hostname2 批改 /etc/security/limits.conf limit 资源限度, successful" result_ok "# $hostname2 批改 /etc/security/limits.conf limit 资源限度, successful" >>$logfilefi# 16. 批改 kernel 配置split1 "# 16. 批改 /etc/sysctl.conf kernel 配置:"split1 "# 16. 批改 /etc/sysctl.conf kernel 配置:" >>$logfilesed -i '/[^fs.|^vm.|^kernel.|^net.|^$]/Id' /etc/sysctl.confmemTotal=$(grep MemTotal /proc/meminfo | awk '{print $2}')MEM=$(expr $(grep MemTotal /proc/meminfo | awk '{print $2}') \* 1024)SHMALL=$(($MEM * 9 / 10 / $(getconf PAGE_SIZE)))SHMMAX=$(($MEM * 9 / 10)) # 这里配置为 90% RAM大小# shmmax(bytes) = shmmni(page size, default 4k) * shmall (page的个数)if [ $SHMALL -lt 2097152 ]; then ## 2097152*4k/1024/1024=8G SHMALL=2097152fiSHMMAX=$((memTotal * 1024 - 1))if [ "$SHMMAX" -lt 4294967295 ]; then ## 4294967295k/1024/1024=4095MB SHMMAX=4294967295fiecho "fs.file-max = 6815744fs.aio-max-nr = 1048576kernel.sem = 250 32000 100 128kernel.shmmni = 4096kernel.shmall = $SHMALLkernel.shmmax = $SHMMAXkernel.panic_on_oops = 1net.core.rmem_default = 262144net.core.rmem_max = 41943044net.core.wmem_default = 262144net.core.wmem_max = 4194304net.ipv4.ip_local_port_range = 10000 65500net.ipv4.conf.all.rp_filter = 2net.ipv4.conf.default.rp_filter = 2net.ipv6.conf.all.disable_ipv6 = 1net.ipv6.conf.default.disable_ipv6 = 1net.ipv4.tcp_max_tw_buckets = 10000net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_keepalive_time = 30net.ipv4.tcp_keepalive_intvl = 10net.ipv4.tcp_retries2 = 12net.ipv4.ip_local_reserved_ports = 15400-15407,20050-20057net.ipv4.tcp_rmem = 8192 250000 16777216net.ipv4.tcp_wmem = 8192 250000 16777216# vm.dirty_background_ratio = 5# vm.dirty_ratio = 40# vm.dirty_expire_centisecs = 500# vm.dirty_writeback_centisecs = 100# vm.min_free_kbytes= 1048576vm.swappiness = 1" >>/etc/sysctl.confsed -i '/^$/Id' /etc/sysctl.confsysctl -psysctl --system >>$logfilescp /etc/sysctl.conf $hostname2:/etc/ >>$logfileecho "sysctl -p;sysctl --system;exit"|ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 批改 /etc/sysctl.conf kernel 配置, Failed" result_err "# $hostname2 批改 /etc/sysctl.conf kernel 配置, Failed" >>$logfile exit 1else result_ok "# $hostname2 批改 /etc/sysctl.conf kernel 配置, successful" result_ok "# $hostname2 批改 /etc/sysctl.conf kernel 配置, successful" >>$logfilefi# 17. 批改启动 runlevelsplit1 "# 17. 批改启动 runlevel=3:"split1 "# 17. 批改启动 runlevel=3:" >>$logfilesystemctl set-default multi-user.target# systemctl get-default #查看默认启动形式是什么,如果显示multi-user.target, 阐明是默认命令行启动# systemctl set-default graphical.target #设置开机默认图形桌面启动# systemctl set-default multi-user.target #设置开机默认命令行启动echo "systemctl set-default multi-user.target;exit"|ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 批改启动 runlevel=3, Failed" result_err "# $hostname2 批改启动 runlevel=3, Failed" >>$logfile exit 1else result_ok "# $hostname2 批改启动 runlevel=3, successful" result_ok "# $hostname2 批改启动 runlevel=3, successful" >>$logfilefi# 18. 设置ssh,避免登录过慢,启用X11 Forwing 转发(默认拜访接收端6000端口split1 "# 18. 设置 /etc/ssh/sshd_config,避免登录过慢,启用X11 Forwing 转发(默认拜访接收端6000端口)"split1 "# 18. 设置 /etc/ssh/sshd_config,避免登录过慢,启用X11 Forwing 转发(默认拜访接收端6000端口)" >>$logfilecp /etc/ssh/sshd_config /etc/ssh/sshd_config_$(date +"%Y%m%d_%H%M%S") && sed -i '/UseDNS no\|^AllowTcpForwarding yes\|X11UseLocalhost no/d' /etc/ssh/sshd_configsed -i '/#UseDNS yes/a\UseDNS no' /etc/ssh/sshd_configsed -i '/#AllowTcpForwarding yes/a\AllowTcpForwarding yes' /etc/ssh/sshd_configsed -i '/#X11UseLocalhost yes/a\X11UseLocalhost no' /etc/ssh/sshd_configegrep "UseDNS|Forwarding|X11" /etc/ssh/sshd_config >>$logfilesystemctl restart sshdscp /etc/ssh/sshd_config $hostname2:/etc/ssh/ >>$logfileecho "systemctl restart sshd;exit"|ssh $hostname2 >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 设置ssh,避免登录过慢,启用X11 Forwing 转发, Failed" result_err "# $hostname2 设置ssh,避免登录过慢,启用X11 Forwing 转发, Failed" >>$logfile exit 1else result_ok "# $hostname2 设置ssh,避免登录过慢,启用X11 Forwing 转发, successful" result_ok "# $hostname2 设置ssh,避免登录过慢,启用X11 Forwing 转发, successful" >>$logfilefi# 19. 设置sudoers,授予oinstall组sudo ALL 权限split1 "# 19. 设置 /etc/sudoers,授予oinstall组sudo ALL 权限:"split1 "# 19. 设置 /etc/sudoers,授予oinstall组sudo ALL 权限:" >>$logfilecp /etc/sudoers /etc/sudoers_$(date +"%Y%m%d_%H%M%S") && sed -i '/oinstall/d' /etc/sudoers && sed -i '/^%wheel/a\%oinstall ALL=(ALL) ALL' /etc/sudoers && grep oinstall /etc/sudoersgrep oinstall /etc/sudoers >>$logfilescp /etc/sudoers $hostname2:/etc/ >>$logfileif [ $? -ne 0 ]; then result_err "# $hostname2 设置sudoers,授予oinstall组sudo ALL 权限, Failed" result_err "# $hostname2 设置sudoers,授予oinstall组sudo ALL 权限, Failed" >>$logfile exit 1else result_ok "# $hostname2 设置sudoers,授予oinstall组sudo ALL 权限, successful" result_ok "# $hostname2 设置sudoers,授予oinstall组sudo ALL 权限, successful" >>$logfilefi# 20. cvu 装置split1 "# 20. cvu 1节点装置,并传送2节点装置:"split1 "# 20. cvu 1节点装置,并传送2节点装置:" >>$logfileif [ -f $GI_HOME/cv/rpm/cvuqdisk*.rpm ]; then rpm -ivh $GI_HOME/cv/rpm/cvuqdisk*.rpm >>$logfile echo "# 2节点装置" >>$logfile scp $GI_HOME/cv/rpm/cvuqdisk*.rpm $hostname2:/tmp/ >>$logfile ssh $hostname2 rpm -ivh /tmp/cvuqdisk*.rpm >>$logfileelse result_err "# not found package cvuqdisk*.rpm" result_err "# not found package cvuqdisk*.rpm" >>$logfile exit 1fiif [ $? -ne 0 ]; then result_err "# cvu 1节点装置,并传送2节点装置, Failed" result_err "# cvu 1节点装置,并传送2节点装置, Failed" >>$logfile exit 1else result_ok "# cvu 1节点装置,并传送2节点装置, successful" result_ok "# cvu 1节点装置,并传送2节点装置, successful" >>$logfilefi# 21. asm 配置# ASM 磁盘组调配split1 "# 21. UDEV 配置磁盘,$hostname1:"split1 "# 21. UDEV 配置磁盘,$hostname1:" >>$logfile>/etc/udev/rules.d/99-oracle-asmdevices.rulesfor i in $asm_ocr_disks; do echo "KERNEL==\"${mapper_prefix}?\",SUBSYSTEM==\"block\", PROGRAM==\"/usr/lib/udev/scsi_id --whitelisted --replace-whitespace --device=$mapper_dir/\$name\",RESULT==\"$(/usr/lib/udev/scsi_id --whitelisted --replace-whitespace --device=$mapper_dir/${mapper_prefix}$i)\", SYMLINK+=\"asm_ocr_disk$i\",OWNER=\"grid\",ACTION==\"add|change\", GROUP=\"asmadmin\",MODE=\"0660\"" >>/etc/udev/rules.d/99-oracle-asmdevices.rules ocr_tmp=$(echo "$ocr_tmp""/dev/asm_ocr_disk$i,")doneexport ocr_fgs=$(echo $ocr_tmp | sed 's/,\//,,\//g')export ocr_diskgs=$(echo $ocr_tmp | sed 's/,$//')for i in $asm_fra_disks; do echo "KERNEL==\"${mapper_prefix}?\",SUBSYSTEM==\"block\", PROGRAM==\"/usr/lib/udev/scsi_id --whitelisted --replace-whitespace --device=$mapper_dir/\$name\",RESULT==\"$(/usr/lib/udev/scsi_id --whitelisted --replace-whitespace --device=$mapper_dir/${mapper_prefix}$i)\", SYMLINK+=\"asm_fra_disk$i\",OWNER=\"grid\",ACTION==\"add|change\", GROUP=\"asmadmin\",MODE=\"0660\"" >>/etc/udev/rules.d/99-oracle-asmdevices.rules fra_tmp=$(echo "$fra_tmp""/dev/asm_fra_disk$i,")doneexport fra_fgs=$(echo $fra_tmp | sed 's/,\//,,\//g')export fra_diskgs=$(echo $fra_tmp | sed 's/,$//')for i in $asm_data_disks; do echo "KERNEL==\"${mapper_prefix}?\",SUBSYSTEM==\"block\", PROGRAM==\"/usr/lib/udev/scsi_id --whitelisted --replace-whitespace --device=$mapper_dir/\$name\",RESULT==\"$(/usr/lib/udev/scsi_id --whitelisted --replace-whitespace --device=$mapper_dir/${mapper_prefix}$i)\", SYMLINK+=\"asm_data_disk$i\",OWNER=\"grid\",ACTION==\"add|change\", GROUP=\"asmadmin\",MODE=\"0660\"" >>/etc/udev/rules.d/99-oracle-asmdevices.rules data_tmp=$(echo "$data_tmp""/dev/asm_data_disk$i,")doneexport data_fgs=$(echo $data_tmp | sed 's/,\//,,\//g')export data_diskgs=$(echo $data_tmp | sed 's/,$//')systemctl restart systemd-udev-trigger.service >>$logfileudevadm control --reload-rules >>$logfileudevadm trigger --type=devices >>$logfileudevadm trigger --type=devices --action=change >>$logfilesleep 10ls -l /dev/ | grep asm >>$logfileif [ $(ssh $hostname1 ls /dev/asm* | wc -l) -lt 5 ]; then result_err "# $hostname1 UDEV 配置磁盘, Failed" result_err "# $hostname1 UDEV 配置磁盘, Failed" >>$logfile echo "$(ssh $hostname1 ls /dev/asm*)" echo "$(ssh $hostname1 ls /dev/asm*)" >>$logfile exit 1else result_ok "# $hostname1 UDEV 配置磁盘, successful" result_ok "# $hostname1 UDEV 配置磁盘, successful" >>$logfile echo "$(ssh $hostname1 ls /dev/asm*)" echo "$(ssh $hostname1 ls /dev/asm*)" >>$logfilefiecho "# $hostname2 UDEV 配置磁盘"echo "# $hostname2 UDEV 配置磁盘" >>$logfilescp /etc/udev/rules.d/99-oracle-asmdevices.rules $hostname2:/etc/udev/rules.d/ >>$logfileecho "systemctl restart systemd-udev-trigger.service;udevadm control --reload-rules;udevadm trigger --type=devices;udevadm trigger --type=devices --action=change;sleep 10;exit"|ssh $hostname2 >>$logfileif [ $(ssh $hostname2 ls /dev/asm* | wc -l) -lt 5 ]; then result_err "# $hostname2 UDEV 配置磁盘, Failed" echo "$(ssh $hostname2 ls /dev/asm*)" result_err "# $hostname2 UDEV 配置磁盘, Failed" >>$logfile echo "$(ssh $hostname2 ls /dev/asm*)" >>$logfile exit 1else result_ok "# $hostname2 UDEV 配置磁盘, successful" echo "$(ssh $hostname2 ls /dev/asm*)" result_ok "# $hostname2 UDEV 配置磁盘, successful" >>$logfile echo "$(ssh $hostname2 ls /dev/asm*)" >>$logfilefi# 格式化磁盘if [ $need_format == "y" ] || [ $need_format == "Y" ];then for i in $(ls -l /dev | grep grid | awk '{print $NF}'); do cnt=$(lsblk -b | grep -w $i | awk '{print $4/1024/1024}') echo -e "\n# 开始格式化 /dev/$i, 大小:${cnt}MB ..." echo "dd if=/dev/zero of=/dev/$i bs=1M count=$cnt" dd if=/dev/zero of=/dev/$i bs=1M count=$cnt echo "# 格式化 /dev/$i 实现" done if [ -z $i ];then result_err "# You need to format the disk, but did not execute it. Please check it." result_err "# You need to format the disk, but did not execute it. Please check it." >>$logfile exit 1 fifi# 22. 预查看split1 "# 22. grid 装置预查看(仅节点1运行):"split1 "# 22. grid 装置预查看(仅节点1运行):" >>$logfileif [ "$(hostname)" == "$hostname1" ]; then echo '#!/usr/bin/expect -f' >/tmp/precheck.sh echo "spawn $GI_HOME/runcluvfy.sh stage -pre crsinst -n $hostname1,$hostname2 -fixup -verbose -method rootexpect { \"*assword:\" {send \"$rootpw\r\";exp_continue} \"*again:\" {send \"$rootpw\r\"}}expect eof" >>/tmp/precheck.sh chmod 777 /tmp/precheck.sh su - grid -c "expect /tmp/precheck.sh" >>$logfile if [ $? -ne 0 ]; then result_err "# grid 装置预查看(仅节点1运行), Failed" result_err "# grid 装置预查看(仅节点1运行), Failed" >>$logfile exit 1 else result_ok "# grid 装置预查看(仅节点1运行), successful" result_ok "# grid 装置预查看(仅节点1运行), successful" >>$logfile fifi# 23. grid 软件装置split1 "# 23. grid 软件装置(仅节点1运行):"split1 "# 23. grid 软件装置(仅节点1运行):" >>$logfile# $ sed -e 's/[\t ]\+$//' gridsetup.rsp | egrep -v "^$|^#|=$"# oracle.install.responseFileVersion=/oracle/install/rspfmt_crsinstall_response_schema_v19.0.0# INVENTORY_LOCATION=/u01/app/oraInventory# oracle.install.option=CRS_CONFIG# ORACLE_BASE=/u01/app/grid# oracle.install.asm.OSDBA=asmdba# oracle.install.asm.OSOPER=asmoper# oracle.install.asm.OSASM=asmadmin# oracle.install.crs.config.scanType=LOCAL_SCAN# oracle.install.crs.config.gpnp.scanName=Jeff-Test-SCAN# oracle.install.crs.config.gpnp.scanPort=1525# oracle.install.crs.config.ClusterConfiguration=STANDALONE# oracle.install.crs.config.configureAsExtendedCluster=false# oracle.install.crs.config.clusterName=Jeff-Test# oracle.install.crs.config.gpnp.configureGNS=false# oracle.install.crs.config.autoConfigureClusterNodeVIP=false# oracle.install.crs.config.clusterNodes=Ora-Jeff-Test-Prod-RAC1:Jeff-Test-VIP1:HUB,Ora-Jeff-Test-Prod-RAC2:Jeff-Test-VIP2:HUB# oracle.install.crs.config.networkInterfaceList=Bond0:10.10.164.0:1,Bond1:11.11.11.0:5# oracle.install.crs.configureGIMR=false# oracle.install.asm.configureGIMRDataDG=false# oracle.install.crs.config.storageOption=FLEX_ASM_STORAGE# oracle.install.asm.SYSASMPassword=Oracle_19C# oracle.install.crs.config.useIPMI=false# oracle.install.asm.storageOption=ASM# oracle.install.asm.diskGroup.name=OCR# oracle.install.asm.diskGroup.redundancy=NORMAL# oracle.install.asm.diskGroup.AUSize=4# oracle.install.asm.diskGroup.disksWithFailureGroupNames=/dev/asm_ocr_diska,,/dev/asm_ocr_diskb,,/dev/asm_ocr_diskc,# oracle.install.asm.diskGroup.disks=/dev/asm_ocr_diska,/dev/asm_ocr_diskb,/dev/asm_ocr_diskc# oracle.install.asm.diskGroup.diskDiscoveryString=/dev/asm*# oracle.install.asm.monitorPassword=Oracle_19C# oracle.install.asm.gimrDG.AUSize=1# oracle.install.asm.configureAFD=false# oracle.install.crs.configureRHPS=false# oracle.install.crs.config.ignoreDownNodes=false# oracle.install.config.managementOption=NONE# oracle.install.config.omsPort=0# oracle.install.crs.rootconfig.executeRootScript=falseif [ -f ${GI_HOME}/gridSetup.sh ] && [ "$(hostname)" == "$hostname1" ]; then cat >/tmp/gridsetup.rsp <<gridsetuprsporacle.install.responseFileVersion=/oracle/install/rspfmt_crsinstall_response_schema_v19.0.0INVENTORY_LOCATION=$ORA_INVoracle.install.option=CRS_CONFIGORACLE_BASE=$GI_BASEoracle.install.asm.OSDBA=asmdbaoracle.install.asm.OSOPER=asmoperoracle.install.asm.OSASM=asmadminoracle.install.crs.config.scanType=LOCAL_SCANoracle.install.crs.config.gpnp.scanName=$SCANNAMEoracle.install.crs.config.gpnp.scanPort=$scanPortoracle.install.crs.config.ClusterConfiguration=STANDALONEoracle.install.crs.config.configureAsExtendedCluster=falseoracle.install.crs.config.clusterName=$cluster_nameoracle.install.crs.config.gpnp.configureGNS=falseoracle.install.crs.config.autoConfigureClusterNodeVIP=falseoracle.install.crs.config.clusterNodes=$hostname1:$VIP1NAME:HUB,$hostname2:$VIP2NAME:HUBoracle.install.crs.config.networkInterfaceList=$pub_netcard:$scan_subnet:1,$priv_netcard:$priv_subnet:5oracle.install.crs.configureGIMR=falseoracle.install.asm.configureGIMRDataDG=falseoracle.install.crs.config.storageOption=FLEX_ASM_STORAGEoracle.install.asm.SYSASMPassword=$db_admin_pwdoracle.install.crs.config.sharedFileSystemStorage.ocrLocations=oracle.install.crs.config.useIPMI=falseoracle.install.asm.storageOption=ASMoracle.install.asm.diskGroup.name=${asm_ocr_dir}oracle.install.asm.diskGroup.redundancy=NORMALoracle.install.asm.diskGroup.AUSize=4oracle.install.asm.diskGroup.disksWithFailureGroupNames=$ocr_fgsoracle.install.asm.diskGroup.disks=$ocr_diskgsoracle.install.asm.diskGroup.diskDiscoveryString=/dev/asm*oracle.install.asm.monitorPassword=$db_admin_pwdoracle.install.asm.gimrDG.AUSize=1oracle.install.asm.configureAFD=falseoracle.install.crs.configureRHPS=falseoracle.install.crs.config.ignoreDownNodes=falseoracle.install.config.managementOption=NONEoracle.install.config.omsPort=0oracle.install.crs.rootconfig.executeRootScript=falseoracle.install.crs.rootconfig.configMethod=gridsetuprsp chmod 777 /tmp/gridsetup.rsp# echo '#!/usr/bin/expect -f' >/tmp/grid_install.sh# echo "spawn su - grid -c \"${GI_HOME}/gridSetup.sh -silent -ignorePrereqFailure -responseFile /tmp/gridsetup.rsp -applyRU $RUs\"#expect {# \"*assword:\" {send \"$rootpw\r\"}#}#expect eof" >>/tmp/grid_install.sh# chmod 777 /tmp/grid_install.sh echo -e "# 开始应用rsp响应文件装置grid软件\n命令: ${GI_HOME}/gridSetup.sh -silent -ignorePrereqFailure -waitforcompletion -responseFile /tmp/gridsetup.rsp -applyRU $RUs\n响应文件内容:\n$(cat /tmp/gridsetup.rsp)" echo -e "# 开始应用rsp响应文件装置grid软件\n命令: ${GI_HOME}/gridSetup.sh -silent -ignorePrereqFailure -waitforcompletion -responseFile /tmp/gridsetup.rsp -applyRU $RUs\n响应文件内容:\n$(cat /tmp/gridsetup.rsp)" >>$logfile# expect /tmp/grid_install.sh >>$logfile su - grid -c "${GI_HOME}/gridSetup.sh -silent -ignorePrereqFailure -waitforcompletion -responseFile /tmp/gridsetup.rsp -applyRU $RUs" >>$logfile sleep 10 if [ $(tail -20 $logfile | grep orainstRoot | wc -l) -lt 1 ];then result_err "# ${GI_HOME}/gridSetup.sh -silent Failed" result_err "# ${GI_HOME}/gridSetup.sh -silent Failed" >>$logfile exit 1 else result_ok "# Grid 软件装置 ${GI_HOME}/gridSetup.sh -silent successful" result_ok "# Grid 软件装置 ${GI_HOME}/gridSetup.sh -silent successful" >>$logfile fi if [ -f $ORA_INV/orainstRoot.sh ] && [ -f $GI_HOME/root.sh ];then echo "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点1运行) " >>$logfile sh $ORA_INV/orainstRoot.sh >>$logfile sh $GI_HOME/root.sh >>$logfile if [ $? -ne 0 ]; then result_err "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点1运行) Failed" result_err "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点1运行) Failed" >>$logfile exit 1 else result_ok "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点1运行) successful" result_ok "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点1运行) successful" >>$logfile if [ "$db_type" == "RAC" ] && [ -n $hostname2 ];then echo "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 开始执行(ssh节点2运行)" echo "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 开始执行(ssh节点2运行)" >>$logfile ssh $hostname2 $ORA_INV/orainstRoot.sh >>$logfile ssh $hostname2 $GI_HOME/root.sh >>$logfile if [ $? -ne 0 ]; then result_err "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点2运行) Failed" result_err "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点2运行) Failed" >>$logfile exit 1 else result_ok "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点2运行) successful" result_ok "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 执行(节点2运行) successful" >>$logfile fi else result_err "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 节点2未运行" result_err "# $ORA_INV/orainstRoot.sh;$GI_HOME/root.sh 节点2未运行" >>$logfile exit fi fi else result_err "# $ORA_INV/orainstRoot.sh or $GI_HOME/root.sh file can not found" result_err "# $ORA_INV/orainstRoot.sh or $GI_HOME/root.sh file can not found" >>$logfile exit 1 fi if [ $? -ne 0 ]; then result_err "# grid 软件装置 Failed" result_err "# grid 软件装置 Failed" >>$logfile exit 1 else result_ok "# grid 软件装置 successful" result_ok "# grid 软件装置 successful" >>$logfile fielse result_err "# ${GI_HOME}/gridSetup.sh not found" result_err "# ${GI_HOME}/gridSetup.sh not found" >>$logfile exit 1fi# As a root user, execute the following script(s):# 1. /u01/app/oraInventory/orainstRoot.sh --> $ORA_INV/orainstRoot.sh# 2. /u01/app/19.3.0/grid/root.sh --> $GI_HOME/root.sh# # Execute /u01/app/oraInventory/orainstRoot.sh on the following nodes:# [Jeff-Test-RAC01, Jeff-Test-RAC02]# Execute /u01/app/19.3.0/grid/root.sh on the following nodes:# [Jeff-Test-RAC01, Jeff-Test-RAC02]# # Run the script on the local node first. After successful completion, you can start the script in parallel on all other nodes.# # Successfully Setup Software with warning(s).# As install user, execute the following command to complete the configuration.# /u01/app/19.3.0/grid/gridSetup.sh -executeConfigTools -responseFile /tmp/gridsetup.rsp [-silent]# 24. ASM 磁盘组增加split1 "# 24. ASM 磁盘组增加 ${asm_fra_dir} \ ${asm_data_dir}(仅节点1运行):"split1 "# 24. ASM 磁盘组增加 ${asm_fra_dir} \ ${asm_data_dir}(仅节点1运行):" >>$logfileif [ -n $fra_diskgs ]; then echo -e "# 增加 ${asm_fra_dir} 执行命令:\nsu - grid -c \"asmca -silent -sysAsmPassword $db_admin_pwd -createDiskGroup -diskString '/dev/asm*' -diskGroupName ${asm_fra_dir} -diskList '$fra_diskgs' -redundancy EXTERNAL -au_size 4\"" echo -e "# 增加 ${asm_fra_dir} 执行命令:\nsu - grid -c \"asmca -silent -sysAsmPassword $db_admin_pwd -createDiskGroup -diskString '/dev/asm*' -diskGroupName ${asm_fra_dir} -diskList '$fra_diskgs' -redundancy EXTERNAL -au_size 4\"" >>$logfile su - grid -c "asmca -silent -sysAsmPassword $db_admin_pwd -createDiskGroup -diskString '/dev/asm*' -diskGroupName ${asm_fra_dir} -diskList '$fra_diskgs' -redundancy EXTERNAL -au_size 4" >>$logfile if [ $? -ne 0 ]; then result_err "# ASM 磁盘组增加 ${asm_fra_dir}, Failed" result_err "# ASM 磁盘组增加 ${asm_fra_dir}, Failed" >>$logfile exit 1 else result_ok "# ASM 磁盘组增加 ${asm_fra_dir}, successful" result_ok "# ASM 磁盘组增加 ${asm_fra_dir}, successful" >>$logfile fielse echo "# No ${asm_fra_dir} Disk input, \$$fra_diskgs:$$fra_diskgs"fiif [ -n $data_diskgs ]; then echo -e "\n# 增加 ${asm_data_dir} 执行命令:\nsu - grid -c \"asmca -silent -sysAsmPassword $db_admin_pwd -createDiskGroup -diskString '/dev/asm*' -diskGroupName ${asm_data_dir} -diskList '$data_diskgs' -redundancy EXTERNAL -au_size 4\"" echo -e "\n# 增加 ${asm_data_dir} 执行命令:\nsu - grid -c \"asmca -silent -sysAsmPassword $db_admin_pwd -createDiskGroup -diskString '/dev/asm*' -diskGroupName ${asm_data_dir} -diskList '$data_diskgs' -redundancy EXTERNAL -au_size 4\"" >>$logfile su - grid -c "asmca -silent -sysAsmPassword $db_admin_pwd -createDiskGroup -diskString '/dev/asm*' -diskGroupName ${asm_data_dir} -diskList '$data_diskgs' -redundancy EXTERNAL -au_size 4" >>$logfile if [ $? -ne 0 ]; then result_err "# ASM 磁盘组增加 ${asm_data_dir}, Failed" result_err "# ASM 磁盘组增加 ${asm_data_dir}, Failed" >>$logfile exit 1 else result_ok "# ASM 磁盘组增加 ${asm_data_dir}, successful" result_ok "# ASM 磁盘组增加 ${asm_data_dir}, successful" >>$logfile fielse echo "# No ${asm_data_dir} Disk input, \$data_diskgs:$data_diskgs"fi# 25. DB 软件装置split1 "# 25. DB 软件装置(仅节点1运行):"split1 "# 25. DB 软件装置(仅节点1运行):" >>$logfile# $ sed -e 's/[\t ]\+$//' db_install_manual.rsp | egrep -v "^$|^#|=$"# oracle.install.responseFileVersion=/oracle/install/rspfmt_dbinstall_response_schema_v19.0.0# oracle.install.option=INSTALL_DB_SWONLY# UNIX_GROUP_NAME=oinstall# INVENTORY_LOCATION=/u01/app/oraInventory# ORACLE_BASE=/u01/app/oracle# oracle.install.db.InstallEdition=EE# oracle.install.db.OSDBA_GROUP=dba# oracle.install.db.OSOPER_GROUP=oper# oracle.install.db.OSBACKUPDBA_GROUP=backupdba# oracle.install.db.OSDGDBA_GROUP=dgdba# oracle.install.db.OSKMDBA_GROUP=kmdba# oracle.install.db.OSRACDBA_GROUP=racdba# oracle.install.db.rootconfig.executeRootScript=false# oracle.install.db.CLUSTER_NODES=ora-Jeff-Test-prod-rac1,ora-Jeff-Test-prod-rac2# oracle.install.db.config.starterdb.type=GENERAL_PURPOSE# oracle.install.db.ConfigureAsContainerDB=false# oracle.install.db.config.starterdb.memoryOption=false# oracle.install.db.config.starterdb.installExampleSchemas=false# oracle.install.db.config.starterdb.managementOption=DEFAULT# oracle.install.db.config.starterdb.omsPort=0# oracle.install.db.config.starterdb.enableRecovery=falsecat >/tmp/db_install.rsp<<dbinstoracle.install.responseFileVersion=/oracle/install/rspfmt_dbinstall_response_schema_v19.0.0oracle.install.option=INSTALL_DB_SWONLYUNIX_GROUP_NAME=oinstallINVENTORY_LOCATION=$ORA_INVORACLE_HOME=$ORA_HOMEORACLE_BASE=$ORA_BASEoracle.install.db.InstallEdition=EEoracle.install.db.OSDBA_GROUP=dbaoracle.install.db.OSOPER_GROUP=operoracle.install.db.OSBACKUPDBA_GROUP=backupdbaoracle.install.db.OSDGDBA_GROUP=dgdbaoracle.install.db.OSKMDBA_GROUP=kmdbaoracle.install.db.OSRACDBA_GROUP=racdbaoracle.install.db.CLUSTER_NODES=$hostname1,$hostname2oracle.install.db.config.starterdb.type=GENERAL_PURPOSEoracle.install.db.isRACOneInstall=falseoracle.install.db.rac.serverpoolCardinality=0oracle.install.db.ConfigureAsContainerDB=false# oracle.install.db.config.starterdb.memoryOption=false# oracle.install.db.config.starterdb.installExampleSchemas=false# oracle.install.db.config.starterdb.managementOption=DEFAULT# oracle.install.db.config.starterdb.omsPort=0# oracle.install.db.config.starterdb.enableRecovery=falseoracle.install.db.rootconfig.executeRootScript=falseSECURITY_UPDATES_VIA_MYORACLESUPPORT=falseDECLINE_SECURITY_UPDATES=truedbinstchmod 777 /tmp/db_install.rspif [ -f $ORA_HOME/runInstaller ]; then echo -e "# 执行 DB 软件装置命令:\nsu - oracle -c \"$ORA_HOME/runInstaller -silent -force -noconfig -ignorePrereq -responseFile /tmp/db_install.rsp -applyRU $RUs\"" echo -e "# 执行 DB 软件装置命令:\nsu - oracle -c \"$ORA_HOME/runInstaller -silent -force -noconfig -ignorePrereq -responseFile /tmp/db_install.rsp -applyRU $RUs\"" >>$logfile su - oracle -c "$ORA_HOME/runInstaller -silent -force -noconfig -ignorePrereq -responseFile /tmp/db_install.rsp -applyRU $RUs" >>$logfile sleep 10 if [ $(tail -20 $logfile | grep root.sh | wc -l) -lt 1 ];then result_err "# $ORA_HOME/runInstaller -silent Failed" result_err "# $ORA_HOME/runInstaller -silent Failed" >>$logfile exit 1 else result_ok "# $ORA_HOME/runInstaller -silent successful" result_ok "# $ORA_HOME/runInstaller -silent successful" >>$logfile fi echo "# root 执行脚本 $ORA_HOME/root.sh (节点1运行)" echo "# root 执行脚本 $ORA_HOME/root.sh (节点1运行)" >>$logfile $ORA_HOME/root.sh >>$logfile if [ $? -ne 0 ]; then result_err "# root 执行脚本 $ORA_HOME/root.sh (节点1运行) Failed" result_err "# root 执行脚本 $ORA_HOME/root.sh (节点1运行) Failed" >>$logfile exit 1 else result_ok "# root 执行脚本 $ORA_HOME/root.sh (节点1运行) successful" result_ok "# root 执行脚本 $ORA_HOME/root.sh (节点1运行) successful" >>$logfile if [ "$db_type" == "RAC" ] && [ -n $hostname2 ]; then echo -e "\n# root 执行脚本 $ORA_HOME/root.sh (节点2运行)" echo -e "\n# root 执行脚本 $ORA_HOME/root.sh (节点2运行)" >>$logfile $ORA_HOME/root.sh >>$logfile fi fi if [ $? -ne 0 ]; then result_err "# DB 软件装置 Failed" result_err "# DB 软件装置 Failed" >>$logfile exit 1 else result_ok "# DB 软件装置 successful" result_ok "# DB 软件装置 successful" >>$logfile fielse result_err "# $ORA_HOME/runInstaller file not found" result_err "# $ORA_HOME/runInstaller file not found" >>$logfile exit 1fi# 26. DBCA 创立数据库split1 "# 26 DBCA 创立数据库(仅节点1运行):"split1 "# 26 DBCA 创立数据库(仅节点1运行):" >>$logfile# $ sed -e 's/[\t ]\+$//' dbca_MANUAL.rsp | egrep -v "^$|^#|=$"# responseFileVersion=/oracle/assistants/rspfmt_dbca_response_schema_v12.2.0# gdbName=JeffTest# sid=Jeff0# databaseConfigType=RAC# policyManaged=false# createServerPool=false# force=false# createAsContainerDatabase=true# numberOfPDBs=1# pdbName=Jeff_pdb# useLocalUndoForPDBs=true# nodelist=jeff-test-rac01,jeff-test-rac02# templateName=/u01/app/oracle/product/19.3.0/db_1/assistants/dbca/templates/New_Database.dbt# emExpressPort=5500# runCVUChecks=TRUE# omsPort=0# dvConfiguration=false# olsConfiguration=false# datafileJarLocation={ORACLE_HOME}/assistants/dbca/templates/# datafileDestination=+DATA/{DB_UNIQUE_NAME}/# recoveryAreaDestination=+FRA# storageType=ASM# diskGroupName=+DATA/{DB_UNIQUE_NAME}/# recoveryGroupName=+FRA# characterSet=AL32UTF8# nationalCharacterSet=UTF8# registerWithDirService=false# skipListenerRegistration=true# variables=ORACLE_BASE_HOME=/u01/app/oracle/product/19.3.0/db_1,DB_UNIQUE_NAME=JeffTest,ORACLE_BASE=/u01/app/oracle,PDB_NAME=,DB_NAME=Jeff,ORACLE_HOME=/u01/app/oracle/product/19.3.0/db_1,SID=Jeff0# initParams=Jeff01.undo_tablespace=UNDOTBS1,Jeff02.undo_tablespace=UNDOTBS2,enable_pluggable_database=true,sga_target=1824MB,db_block_size=8192BYTES,cluster_database=true,standby_file_management=AUTO,family:dw_helper.instance_mode=read-only,log_archive_dest_1='LOCATION=+FRA',nls_language=AMERICAN,filesystemio_options=SETALL,dispatchers=(PROTOCOL=TCP) (SERVICE=Jeff0XDB),diagnostic_dest={ORACLE_BASE},remote_login_passwordfile=exclusive,db_create_file_dest=+DATA/{DB_UNIQUE_NAME}/,db_create_online_log_dest_2=+FRA,db_create_online_log_dest_1=+DATA,nls_date_format="YYYY/MM/DD HH24:MI:SS",parallel_force_local=TRUE,audit_file_dest={ORACLE_BASE}/admin/{DB_UNIQUE_NAME}/adump,processes=5000,pga_aggregate_target=609MB,Jeff01.thread=1,Jeff02.thread=2,nls_territory=AMERICA,undo_retention=10800,session_cached_cursors=200,local_listener=-oraagent-dummy-,db_recovery_file_dest_size=18000MB,db_unique_name=JeffTest,optimizer_adaptive_plans=FALSE,open_cursors=1000,control_file_record_keep_time=31,log_archive_format=arch_%t_%s_%r.arc,compatible=19.0.0,sga_max_size=1000MB,db_name=Jeff,archive_lag_target=1800,result_cache_max_size=0MB,db_files=2000,enable_ddl_logging=TRUE,Jeff01.instance_number=1,Jeff02.instance_number=2,db_recovery_file_dest=+FRA,audit_trail=db# sampleSchema=false# memoryPercentage=40# databaseType=MULTIPURPOSE# automaticMemoryManagement=false# totalMemory=0cat >/tmp/dbca.rsp<<dbcarspresponseFileVersion=/oracle/assistants/rspfmt_dbca_response_schema_v12.2.0gdbName=$DB_UNIQUE_NAMEsid=$ORA_SID_PREFIXdatabaseConfigType=RACRACOneNodeServiceName=policyManaged=falsecreateServerPool=falseforce=falsecreateAsContainerDatabase=truenumberOfPDBs=1pdbName=$PDB_SIDuseLocalUndoForPDBs=truepdbAdminPassword=$ORAPWDnodelist=$hostname1,$hostname2templateName=$ORA_HOME/assistants/dbca/templates/New_Database.dbtsysPassword=$ORAPWDsystemPassword=$ORAPWDserviceUserPassword=$ORAPWDemConfiguration=emExpressPort=5500runCVUChecks=TRUEdbsnmpPassword=$ORAPWDomsPort=0dvConfiguration=falseolsConfiguration=falsedatafileJarLocation={ORACLE_HOME}/assistants/dbca/templates/datafileDestination=+${asm_data_dir}/{DB_UNIQUE_NAME}/recoveryAreaDestination=+${asm_fra_dir}storageType=ASMdiskGroupName=+${asm_data_dir}/{DB_UNIQUE_NAME}/asmsnmpPassword=$ORAPWDrecoveryGroupName=+${asm_fra_dir}characterSet=AL32UTF8nationalCharacterSet=UTF8registerWithDirService=falseskipListenerRegistration=truevariables=ORACLE_BASE_HOME=$ORA_HOME,DB_UNIQUE_NAME=$DB_UNIQUE_NAME,ORACLE_BASE=$ORA_BASE,PDB_NAME=$PDB_SID,DB_NAME=$DB_NAME,ORACLE_HOME=$ORA_HOME,SID=$ORA_SID_PREFIXinitParams=$ORA_SID1.undo_tablespace=UNDOTBS1,$ORA_SID2.undo_tablespace=UNDOTBS2,enable_pluggable_database=true,sga_target=$sgasize,db_block_size=8192BYTES,cluster_database=true,standby_file_management=AUTO,family:dw_helper.instance_mode=read-only,log_archive_dest_1='LOCATION=+${asm_fra_dir}',nls_language=AMERICAN,filesystemio_options=SETALL,dispatchers=(PROTOCOL=TCP) (SERVICE=${ORA_SID_PREFIX}XDB),diagnostic_dest={ORACLE_BASE},remote_login_passwordfile=exclusive,db_create_file_dest=+${asm_data_dir}/{DB_UNIQUE_NAME}/,db_create_online_log_dest_2=+${asm_fra_dir},db_create_online_log_dest_1=+${asm_data_dir},nls_date_format="YYYY/MM/DD HH24:MI:SS",parallel_force_local=TRUE,audit_file_dest={ORACLE_BASE}/admin/{DB_UNIQUE_NAME}/adump,processes=$processes,pga_aggregate_target=$pgasize,$ORA_SID1.thread=1,$ORA_SID2.thread=2,nls_territory=AMERICA,undo_retention=10800,session_cached_cursors=200,local_listener=-oraagent-dummy-,db_recovery_file_dest_size=$recovery_size,db_unique_name=$DB_UNIQUE_NAME,optimizer_adaptive_plans=FALSE,open_cursors=1000,max_dump_file_size='1024m',control_file_record_keep_time=31,log_archive_format=arch_%t_%s_%r.arc,compatible=19.0.0,sga_max_size=$sga_max_size,db_name=$DB_NAME,archive_lag_target=1800,result_cache_max_size=0MB,db_files=2000,enable_ddl_logging=TRUE,$ORA_SID1.instance_number=1,$ORA_SID2.instance_number=2,db_recovery_file_dest=+${asm_fra_dir},audit_trail=dbsampleSchema=falsememoryPercentage=40databaseType=MULTIPURPOSEautomaticMemoryManagement=falsetotalMemory=0dbcarspchmod 777 /tmp/dbca.rspif [ $needcdb != "y" ] && [ $needcdb != "Y" ];then echo "# Do not need CDB" sed -i 's/createAsContainerDatabase=true/createAsContainerDatabase=false/g' /tmp/dbca.rsp if [ $needpdb != "y" ] && [ $needpdb != "Y" ];then sed -i "s/numberOfPDBs=1/numberOfPDBs=0/g" /tmp/dbca.rsp sed -i "s/PDB_NAME=$PDB_SID,//g" /tmp/dbca.rsp sed -i "/PDB_NAME=$PDB_SID/d" /tmp/dbca.rsp fificat /tmp/dbca.rsp >>$logfileecho "# 开始 DBCA 建库"echo "# 开始 DBCA 建库" >>$logfilesu - oracle -c "dbca -silent -createDatabase -ignorePrereqFailure -responseFile /tmp/dbca.rsp -redoLogFileSize 200 -useOMF true -enableArchive true -emConfiguration NONE"if [ $? -ne 0 ];then result_err "# DBCA 建库失败,Failed" result_err "# DBCA 建库失败,Failed" >>$logfile exit 1else result_ok "# DBCA 建库胜利,successful" result_ok "# DBCA 建库胜利,successful" >>$logfilefi

March 16, 2023 · 28 min · jiezi

关于bash:shell-命令提示符中显示时间qbit

前言本文对 Ubuntu 20.04 实用bashbash 版本 5.0.17配置 export PS1="\u@\h: \$PWD/ \D{%Y-%m-%d} \t\n$ "后果 qbit@qhost: /home/qbit/ 2023-02-16 17:13:22$ dateThu 16 Feb 2023 05:13:23 PM CST能够将上述配置加到 .bashrc 中fish编辑文件 ~/.config/fish/functions/fish_prompt.fish (目录或文件不存在就新建),模板参照 /usr/share/fish/functions/fish_prompt.fish function fish_prompt if not set -q __fish_prompt_hostname set -g __fish_prompt_hostname (hostname) end set_color -o cyan echo -n -s "$USER" @ "$__fish_prompt_hostname" ": " set_color -o green echo -n (prompt_pwd) echo -n ' ' echo -n (date +"%Y-%m-%d %H:%M:%S" ) echo '' echo -n "\$ " set_color normalendfish 的 ~/.config/fish/config.fish 对标 bash 的 .bashrc编辑 ~/.config/fish/config.fish 显示残缺门路 ...

February 16, 2023 · 1 min · jiezi

关于bash:TIL-在-Finder-中打开-iOS-模拟器的文件系统

分享两个脚本用于关上 iOS simulator 的设施文件系统与特定 App 的文件系统 设施文件系统设施文件系统是指从 simulator 的“文件”利用中的 On My iPhone 这里看到的文件: open "`xcrun simctl get_app_container booted com.apple.DocumentsApp groups | grep LocalStorage | awk -F'\t' '{print $2}'`/File Provider Storage" -a Finder运行即可: App 文件系统是指每个 App 本人独立的文件系统。 #!/bin/bashPACKAGE_NAME="${1:-default.package.name}" # 默认值open `xcrun simctl get_app_container booted $PACKAGE_NAME data` -a Finder保留为脚本文件,加执行权限,执行: ./sim-app-fs.sh your.package.name 不想保留脚本文件就把 $PACKAGE_NAME 替换成理论的包名间接执行。 补充阐明xcrun simctl 仿佛有 bug cli 帮忙信息里说能够指定特定的 group identifier 来获取指定 group 的门路:但实际上如果指定了 identifier 会认为是谬误的参数,还是打印帮忙信息:这货色也没有官网文档,网上一个用 identifier 的例子都找不到,十有八九是 bug 了倡议配合 raycast 这类工具应用脚本,体验很好 ...

January 31, 2023 · 1 min · jiezi

关于bash:详细说明bashprofile文件

bash的环境配置文件获得bash时须要残缺的登录流程,简称login shell。login shell个别只读取两个配置文件: /etc/profile :零碎整体的设置~/.bash_profile或~/.bash_login或~/.profile:用户集体设置/etc/profile每个用户登录获得bash,肯定会读取的配置文件。不倡议批改 该文件设置的次要变量: PATH:依据UID决定PATH变量要不要含有sbin的系统命令目录MAIL:依据账户设置好的用户的mailbox到/var/spool/mail账户名USER:依据用户的账户设置此变量内容HOSTNAME:依据主机的hostname命令决定此变量内容HISTSIZE:历史命令记录条数umask:包含root默认为022,而个别用户为002等并且默认依序调用以下文件: /etc/profile.d/*.sh:/etc/profile.d目录下sh后缀的问价都会被执行,该目录下次要标准了bash界面的色彩、语系、ll与ls命令的别名、which别名、vi别名等/etc/locale.conf:这个文件由/etc/profile.d/lang.sh调用,决定了bash默认应用那个语系,其中最重要的是LANG/LC_ALL这个变量的设置/usr/share/bash-completion/completiopns/*:命令补全、文件名补全、命令的选项/参数补全,由/etc/profile.d/bash_completyion.sh文件加载执行~/.bash_profilelogin shell的bash环境时候,只会执行上面三个文件中的其中一个,依序 ~/.bash_profile~/.bash_login~/.profile如果~/.bash_profile这个不存在,才会读取~/.bash_login。 .bash_profileGet the aliases and functionsif [ -f ~/.bashrc ]; then. ~/.bashrcfi User specific environment and startup programsexport PS1="[\u@\h \w]&dollar; " set Java environmentJAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.45.x86_64[JACOCO_COMMENT]JAVA_HOME=/xxxx/jdk7export JAVA_HOME=/home/xxoo/jacoco/jdk/jdk7PATH=$JAVA_HOME/bin:$PATHCLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarexport JAVA_HOMEexport PATHexport CLASSPATHalias l='ls -ltr --color=none'alias ls='ls --color=none'alias cls="clear"export JACOCO_AGENT_JAR_PATH=/home/xxoo/jacoco/jacoco/jacoco-0.8.1-rel/lib/jacocoagent.jar 该文件第三行的if语句是判断是否存在~/.bashrc文件,若存在就读入~/.bashrc文件的配置。读取bash的配置文件(~/.bashrc等),次要通过命令source读取。 对于jacoco次要是上面两个全局变量: export JAVA_HOME=/home/xxoo/jacoco/jdk/jdk7export JACOCO_AGENT_JAR_PATH=/home/xxoo/jacoco/jacoco/jacoco-0.8.1-rel/lib/jacocoagent.jarJAVA_HOME:设置零碎java命令为jacoco目录下的java,其中jacoco下的java是一个脚本文件JACOCO_AGENT_JAR_PATH:应用java启动利用时候增加的一个参数其余配置文件: ~/.bash_history:默认状况下,历史命令记录在这个文件外面,记录条数则与变量HISTSIZE变量无关,每次登陆bash,bash会读取这个文件,并且把所有历史命令读入到内存。~/.bash_logout:这个问价次要是【当你登记bash后,零碎帮你做完什么操作才来到】,默认状况是清理屏幕信息,也能够吧一些备份或者重要的工作写在这个文件外面(清理缓存)。参考链接:Understanding bash_profile file in Linux

July 19, 2022 · 1 min · jiezi

关于bash:Bash终端快捷键

Bash快捷键解析Bash提供了许多应用的快捷键操作,可能在实际操作终端命令行是极大提高效率。这些快捷方式终端对GUN Readline库的依赖关系实现的。一旦你用惯了这些快捷键,就能疾速执行一些命令,而不必在键盘上大幅度的挪动。这些快捷键分为几类:导航快捷键、文本输出快捷键、命令历史快捷键和其余快捷键。 导航快捷键利用左箭头和右键头在一行中向前或向后挪动是十分常见的,不过还有其余一些选项也能够让你不必把手从“home”键上挪开。 快捷键 动作Ctral+a 将光标移到本行的开始处Ctrl+e 将光标移到本行的结尾处Ctrl+b(或左箭头键) 将光标回退一个字符Ctrl+f(或右箭头键) 将光标后退一个字符文本输出快捷键每个人都晓得用Backspace键能够删除前一个字符,但手指要挪动肯定的间隔才可能到这个按键,并且它一次只能删除一个字符,而应用快捷键能够疾速的删除整行文本。 快捷键 动作Ctrl+d 删除后一个字符Ctrl+u 从行的结尾剪切至行的开端Ctrl+y yank(即粘帖)之前剪切的文本Ctrl+t transpose(即替换)前两个字符地位Backspace键 删除前一个字符Ctrl+k 从光标开始剪切至行的开端命令历史快捷键快捷键 动作Ctrl+p(或向上箭头) 获取前一个历史命令Ctrl+n(或向下箭头) 获取后一个历史命令Ctrl+r 对历史命令的反向搜寻反向搜寻命令在理论应用中特地有用。如果咱们想要从新执行几天应用过的一个命令,然而只依稀记得命令中的个别重要单词,那么反向搜寻就能派上用途。参考链接:Bash Shortcuts Every Linux User Needs to Know

July 5, 2022 · 1 min · jiezi

关于bash:shell脚本编程学习笔记运算符

shell波及数字计算的理论场景感觉绝对较少,更多场景是关系运算。bash也不提供数字计算能力,须要通过expr实现。这里简略记录expr罕用数字计算形式,其余篇幅具体记录关系运算。 数字计算expr 是表达式计算工具,用于实现表达式的求值操作。 a=1b=2# ``操作符用于运行外部的表达式并返回执行后果sum=`expr ${a} + ${b}`# $()操作符与``性能一样。理论场景中举荐只应用其中一种。diff=$(expr ${b} - ${a})# mac中 expr 能够应用 $(()) 代替diff=$((${b} - ${a}))expr罕用运算符:加(+),减(-),乘(*),除(/),取余(%)。其中 * 为保留字,应用时须要转译:expr 2 /* 2 关系运算shell中应用较多的场景之一就是if-else判断。shell提供了几种模式 if testif []if [[]]

June 20, 2022 · 1 min · jiezi

关于bash:利器-Terminal-Shell-改造记录-Windows-Terminal-Oh-My-ZSH-Tmux

“利器”系列阐明:“工欲善其事必先利其器”,有了称手好用的工具,能力最大水平施展出本人的能力,进步生产力和效率,防止有效加班。因而,“利器”系列将会记录我对于各种工具的革新,以及我是如何组合利用它们施展价值。 这篇文章次要记录我 Terminal & Bash 革新,Terminal & Bash 是所有程序员在新的开发环境下第一个接触也是最常接触的中央,因而,这里的作为“利器”系列的第一篇,分享给大家。 注:以下内容中,波及到下载的链接,均曾经过国内网络优化地址,不便大家疾速下载 Terminal :Windows TerminalWindows环境下,我抉择 Windows Terminal 抉择起因:其余的 Terminal 存在一个不好解决的问题,就是在应用近程Tmux的状况下,无奈应用鼠标抉择复制粘贴,同时Windows Terminal 也领有不输其余 Terminal 个性化配置性能 装置办法:间接在微软利用商店搜寻“Windows Terminal”装置即可。 配置有两种配置形式,一种是交互式,另一种是编辑配置文件setttings.json,倡议首次配置应用交互式的配置形式即可。配置实现后,能够将配置文件导出备份到云盘上,不便当前间接应用。 我次要批改了字体和字号,字号设置为14,不便大屏幕浏览,字体用的是“FiraCode”,这是一种针对于编程的字体,具体的介绍能够看这里:FiraCode 字体的官网下载链接:FiraCode.zip 阿里云下载链接: 「firacode」,点击链接保留,或者复制本段内容,关上「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。 链接:https://www.aliyundrive.com/s...Shell 配置因为我个别都是在本地通过SSH近程连贯到服务器Ubuntu零碎上,因而,这里只探讨Ubuntu环境下的Shell配置。 装置 ZSH在Ubuntu零碎下,执行 sudo apt install -y zsh curl wget git tmux装置 Oh My ZshOh My Zsh 是 Zsh 的配置管理器,包含插件,主题等的配置,并且曾经默认了许多实用功能,集成了相当多的插件(大部分默认不启用)有趣味的敌人能够进入它的官网获取更多信息: Oh My Zsh - a delightful & open source framework for Zsh 想疾速理解的敌人,能够看看它的Cheatsheet: https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet 装置过程 cd ~wget https://pd.zwc365.com/https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh# 设置远端地址为github镜像地址Export REMOTE=https://hub.fastgit.org/ohmyzsh/ohmyzsh.gitsh install.sh装置 starshipsh -c "$(curl -fsSL https://pd.zwc365.com/https://starship.rs/install.sh)" -- -B https://hub.fastgit.org/starship/starship/releasesecho 'eval "$(starship init zsh)"' >> ~/.zshrc装置 Shell 语法高亮插件插件地址: ...

November 28, 2021 · 1 min · jiezi

关于bash:学点bash装B-快速移动光标

学点bash,装B the-art-of-command-line ctrl-w 删除你键入的最初一个单词 ctrl-u 删除行内光标所在位置之前的内容 ctrl-k 删除光标至行尾的所有内容 ctrl-l 能够清屏 ctrl-a 能够将光标移至行首 ctrl-e 能够将光标移至行尾 option-b 和 option-f 能够以单词为单位挪动光标 若按单词挪动生效,请按文章疏导配置 焦虑的头发都要掉了,尝试扭转下本人原文地址

October 21, 2021 · 1 min · jiezi

关于bash:Bash-笔记

<meta charset="utf-8" emacsmode="-*- markdown -*-"> <link rel="stylesheet" href="https://casual-effects.com/markdeep/latest/journal.css?"> 本文有多个主题: SHell 代码的实质成对符、结尾符、退出码,和一些习惯玩玩并行各个主题互相独立,可间接跳到你想看的那个。 SHell 代码的实质有句话说是, Bash 上所有皆字符串。 猜想起因: Bash 是被造出来用来兼顾 Linux (或者别的类 *nix 零碎)的过程交互合作的,而过程间通信时所用数据的类型肯定是字符串。 示例: 上面几行的意义是齐全一样的 echo aecho 'a'echo "a"echo \a这里 a 自身就是个别字符串了,所以不论是用引号打消性能还是用本义打消性能,它都依然是个别字符。 上面几行的意义是齐全一样的 echo 'foo bar'echo "foo bar"echo foo' 'barecho foo" "barecho foo\ \ bar上面几行的意义是齐全一样的 v1=foo v2=ar ; echo $v1\ \ b$v2v3=\ \ ; echo foo"$v3"barv4=' ' ; echo foo"$v4"bar如果下面的 v3 v4 都是只有一个空格字符串而不是像下面那样是两个的话,应用时就能够不用被双引号突围,就像这样: v5=\ ; echo foo${v5}bar 大括号能够清晰形容变量名的边界。没有歧义的时候能够省略大括号。 上面几行是意义齐全一样的 bash -c 'echo foo\ \ bar'bash -c "echo foo\ \ bar"bash -c 'echo foo" "bar'bash -c "echo foo' 'bar"v=' ' ; bash -c "echo foo'$v'bar"上面几行是意义齐全一样的(我感觉这是比拟能体现「所有皆字符串」的中央) ...

October 20, 2021 · 3 min · jiezi

关于bash:研发环境手册

前言拿到新设施的第一天,配置环境总结。实用于: 设施环境:macOS研发前端开发(步骤5、6、7)注释首先,装置homebrew官网地址:https://brew.sh/index_zh-cn官网shell: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"国内镜像:https://www.cnblogs.com/liyih...国内shell【举荐】: /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"注意事项: 装置过程中设施呈现装置命令行工具的弹窗,须要等装置完命令行工具之后再持续进行。只用国内镜像源会快很多二 装置git【依赖homebrew】brew install git配置: git config —global user.name "user-name"git config —global user.email "user-email"三 装置item2下载地址: https://iterm2.com/downloads.... 四 装置zsh官网地址: https://ohmyz.sh/#install官网shell: sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"举荐shell: sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"五 装置nvm地址: https://github.com/nvm-sh/nvm先应用cur 来装置,如果装置失败则应用git clone 办法来装置最初须要配置环境变量到对应的配置文件上 六 装置node【依赖nvm】nvm install node #默认装置最新版本node七 装置nrm (镜像源管理工具)npm -g install nrm

September 29, 2021 · 1 min · jiezi

关于bash:在bash中从一个文件中读取文件名称并检查当前目录是否保护这些文件

目标:在bash中从一个文件中读取文件名称,并查看当前目录下的所有文件,若不存在则返回提醒实现:check_file.sh #!/bin/bash# 获取以后门路CRTDIR=$(pwd)i=0# 从保留了文件名称的txt文件中获取文件名,并生成数组for line in $(cat filelist.txt)do file_list[$i]=${line%%[[:cntrl:]]} ((i++))done# 查看数组中的文件是否存在于当前目录及所有的子目录下for FILE in "${file_list[@]}"do file_count=$(find $CRTDIR -name $FILE | wc -l) if [[ $file_count -eq 0 ]] then echo "Warning: $FILE not found in $CRTDIR!" fidone应用: 将check_file.sh与filelist.txt两个文件放到须要查看的目录下Windows零碎下,能够用Git Bash,先转到该目录输出./check_file.sh, 运行脚本例子: # file.txt1.pdf2.cad3.txt4.exe// 目录构造BashTest├─ 1│ └─ 3.txt├─ check_file.sh└─ filelist.txt$ ./check_file.shWarning: 1.pdf not found in /g/BashTest!Warning: 2.cad not found in /g/BashTest!Warning: 4.exe not found in /g/BashTest!人能常清静,天地悉皆归

July 3, 2021 · 1 min · jiezi

关于bash:For-loop-in-bash

10 Bash for Loop In One Line ExamplesBash For Loop Examples In LinuxWhat Is Bash in Linux? Bash for Loop In one Line with items# for i in 1 2 3 4 5 ; do echo "$i" ; done# for i in {1..5} ; do echo "$i" ; done# for i in {1..5..1};do echo "$i" ; done# for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus; do echo $planet; doneBash for loop C style In One Line with items# for ((i=1;i<=5;i++));do echo $i;doneBash For Loop In one line with Command Output# for i in `seq 1 5`;do echo $i ;done# for i in `cat test`;do dig $i +short ;done# for i in `awk '{print $1}' test` ;do ping -c 2 $i ;doneBash For Loop In one Line with variables# for i in $(cat test);do dig $i +short ;done# a="a b c"# for i in $a;do echo $i;doneabc# a=(a b c)# for i in ${a[@]};do echo $i;doneabc# for i in $(seq 1 5);do echo $i ;doneBash For Infinite Loop In one Line# for (( ; ; )); do echo "Hello World!"; done# while true; do echo "Hello World!"; done# while :; do echo "Hello World!"; doneBash For Loop In One Line with Files# for i in *; do echo "Found the following file: $i"; done# for i in `cat filelist.txt`; do echo ${i}; done;if a line may include spaces better use a while loop:# cat filelist.txt | while read LINE; do echo "${LINE}"; done10 Bash for Loop In One Line ExamplesBash For Loop Examples In LinuxWhat Is Bash in Linux? ...

June 13, 2021 · 2 min · jiezi

关于bash:for-循环与位置参数

word splitting(分词)如果没有在双引号中,变量在进行参数扩大、命令替换和算术扩大之后,shell 会对变量进行分词,比方: $ echo a b c da b c dshell 将 $IFS 的每个字符作为分隔符,如果 $IFS 是 unset 的,则有默认值 <space><tab><newline>。 分词的时候,首先疏忽变量首位的空白符 <space><tab><newline>,再分隔失去单词。 判断 $IFS 是否 unset 的办法。 // 文件test#!/usr/bin/bashif [ -v IFS ];then echo ==\$IFS=$IFS==else echo ==\$IFS is unset==fi$ ./test==$IFS= ==for 循环中的 $* 与 $@$* 与 $@ 都能示意所有的地位参数。 未在双引号中:二者用法统一,都会进行分词。 // 文件test#!/usr/bin/bashecho '==$*=='for name in $*doecho $namedoneecho '==$@=='for name in $@doecho $namedone$ ./test a s d fff==$*==asdfff==$@==asdfff在双引号中:$* 会先分词,再以 $IFS 的第一个字符为分隔符,合并成一个字符串; "$*" 相当于 "$1c$2c…",c 示意 $IFS 的第一个字符。 ...

May 29, 2021 · 1 min · jiezi

关于bash:ifuntilwhile中的testcommands

exit status上一个命令执行完后,退出时返回的状态值。 0 示意胜利;非0示意失败。 在命令行中能够打印查看上一个状态值 $ echo $?依赖于 exit status 的 if、until、whileuntil 的语法: until test-commands; do consequent-commands; donewhile 的语法 while test-commands; do consequent-commands; doneif 的语法 if test-commands; then consequent-commands;[elif more-test-commands; then more-consequents;][else alternate-consequents;]fitest-commands 执行之后,if、until、while 依赖于它的 exit status: 为 0 时,if 执行;为 1 时,until 执行;为 0 时,while 执行。test-commands 蕴含的状况一组或多组管道组成 test-commands多组管道之间能够由 ;, &, &&, 或 ||分隔,由 ;, &, 或 换行 完结;exit status 由最初一组管道的 exit status 决定;一个或多个命令组成一个管道,由| 或 |& 分隔,由最初一个命令的 exit status 决定管道的 exit status;一般而言,单个命令执行胜利,状态值为0。 ...

May 29, 2021 · 1 min · jiezi

关于bash:bash-字符串

\ 反斜杠(本义)紧跟着的 \ 的字符字面量会被保留,输入的时候去掉 \。 一个特例是 \newline (反斜杠+换行符),这个组合会被当做长字符串换行,输入的时候将 \newline (反斜杠+换行符)移除、疏忽。 $ echo \aa$ echo \aaaaaa$ echo aaa\> ssssaaassss'' 单引号无奈应用本义 $ echo 'aaa'aaa"" 双引号根本作用:保留字面量 特例: '$': # $name 援用变量名$ test=asdfg$ echo "$test"asdfg$ echo "11${test}22"11asdfg22# $(command) 执行命令,将后果扩大为字符串$ echo "aaaa$(ls)ssss"aaaa1.txt2.txtssss# $(( expression )) 执行算术表达式,将后果扩大为字符串$ echo "aaaa$(( 3+4 ))ssss"aaaa7ssss'`': # `command` 执行命令,将后果扩大为字符串$ echo "aaaa`ls`ssss"aaaa1.txt2.txtssss'\': # 反斜杠+一般字符$ echo "\a"\a# '$', '`', '"', '\', or 'newline'(换行)# 反斜杠+以上几个特殊符号,输入的时候反斜杠会被删除;其中,`newline` 也会被删除$ echo "\$\`\"==\> =="$`"====# 不论开启或者敞开历史扩大的性能,`反斜杠+!`的输入都同 `反斜杠+一般字符` 一样$ echo "\!-1"\!-1history expansion 开启时,'!': ...

May 29, 2021 · 1 min · jiezi

关于bash:exec-c-及-ENVSUPATH

验证问题以及发现问题验证 manual page 中 exec -c command 不会开启新过程,但环境变量为空。验证形式:打印过程id echo $BASHPID打印环境变量PATH echo $PATH打印所有环境变量 export -p // 文件 test1#!/usr/bin/bashset -e./test2// 文件 test2#!/usr/bin/bashset -eecho '2==PID='$BASHPIDecho '2==PPID='$PPIDecho '2==PATH='$PATHecho '2==export=='`export -p`exec -c ./test3// 文件 test3#!/usr/bin/bashset -eecho '3==PID='$BASHPIDecho '3==PPID='$PPIDecho '3==PATH='$PATHecho '3==export='`export -p`//执行 $ ./test12==PID=286972==PPID=286962==PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin2==export==declare -x DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus" declare -x HOME="用户主目录" declare -x LANG="en_US.UTF-8" declare -x LC_TERMINAL="iTerm2" declare -x LC_TERMINAL_VERSION="3.3.8" declare -x LESSCLOSE="/usr/bin/lesspipe %s %s" declare -x LESSOPEN="| /usr/bin/lesspipe %s" declare -x LOGNAME="登录用户" declare -x LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:" declare -x MOTD_SHOWN="pam" declare -x OLDPWD="用户目录" declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin" declare -x PWD="当前目录名" declare -x SHELL="/bin/bash" declare -x SHLVL="3" declare -x SSH_CLIENT="192.168.0.101 51426 22" declare -x SSH_CONNECTION="192.168.0.101 51426 192.168.0.121 22" declare -x SSH_TTY="/dev/pts/0" declare -x TERM="xterm-256color" declare -x USER="用户名" declare -x XDG_DATA_DIRS="/usr/local/share:/usr/share:/var/lib/snapd/desktop" declare -x XDG_RUNTIME_DIR="/run/user/1000" declare -x XDG_SESSION_CLASS="user" declare -x XDG_SESSION_ID="204" declare -x XDG_SESSION_TYPE="tty"3==PID=286973==PPID=286963==PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin3==export=declare -x OLDPWD declare -x PWD="当前目录名" declare -x SHLVL="1"察看输入test2 同 test3 的过程id、父过程id雷同,后者的环境变量(export -p)能够看做为空(PWD 是当前目录名,SHLVL 是 shell 深度)。 ...

May 29, 2021 · 1 min · jiezi

关于bash:前端面试每日-31-第704天

明天的知识点 (2021.03.20) —— 第704天 (我也要出题)[html] 在微信的H5页面不能下载如何解决?[css] word-spacing有什么作用?[js] 写一个办法在肯定工夫内无任何操作时执行某个事件[软技能] 你有本人写过bash脚本吗?都有写过哪些脚本?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

March 20, 2021 · 1 min · jiezi

关于bash:获取脚本自身路径

在写 gar 脚本的时候,我须要在 gar 脚本在运行时确定它本身在文件系统中所处目录的门路。基于该门路,可将 gar.css 文件部署到文档我的项目的根目录下,因为 gar.css 与 gar 脚本在同一目录下,后者须要依据本身的地位方能找到它,否则就只能由 gar 脚本的用户提供 gar.css 的门路,有所不便。 BASH_SOURCE有人说,用以下语句可取得脚本本身门路 my_path=$(cd $(dirname "BASH_SOURCE[0]") && pwd)我思考了一会。真的思考了……甚至还看了看 Bash reference manual 里对 BASH_SOURCE 的形容: 这个形容里有 FUNCNAME,我又在 manual 里找到了以下形容: 谁晓得它们在说什么呢?目前,我只晓得 BASH_SOURCE 是个数组变量,存储了一组源文件名(Source filename)。百闻不如一见,我须要 echo 一下 ${BASH_SOURCE[0]}: #!/bin/bashecho ${BASH_SOURCE[0]}我将上述脚本取名为 foo,将其置于我定义的一个专用于寄存脚本的目录内,并赋其可执行权限,而后执行 foo, $ foo/home/garfileo/.my-scripts/foo换一种形式执行 foo, $ bash /home/garfileo/.my-scripts/foo/home/garfileo/.my-scripts/foo再换一种形式, $ bash $(foo)/home/garfileo/.my-scripts/foo再换一种形式, $ source $(foo)/home/garfileo/.my-scripts/foo在 Bash 的语法里,$(...) 称为命令替换,即开拓一个子 Shell,在其中执行括号内的命令,并以文本的模式返回后果。 上述试验揭示的是,foo 脚本在运行时,能够确定本人身处何处。foo 能够,gar 也能够。 BASH_SOURCE[0] 与 $0 有什么区别?仿佛通过命令行的第一个参数 $0 也能令一个脚本获知本身所处地位。将 foo 改为 ...

March 16, 2021 · 2 min · jiezi

关于bash:身在何处

我是 Gar,我想讲一下我本人。我想了好几个结尾,最初决定先从确定本人身在何处讲起。 BASH_SOURCE一开始,我不晓得本人身在何处,为此寻访天下。有人山盟海誓,说我在 $(cd $(dirname "BASH_SOURCE[0]") && pwd)我思考了一会。真的思考了……甚至还看了看 Bash reference manual 里对 BASH_SOURCE 的形容: 这个形容里有 FUNCNAME,我又在 manual 里找到了以下形容: 然而我和你一样,思考的是,谁晓得它们在说什么呢?目前,我只晓得 BASH_SOURCE 是个数组变量,存储了一组源文件名(Source filename)。 百闻不如一见,我要 echo 一下 ${BASH_SOURCE[0]}: #!/bin/bashecho ${BASH_SOURCE[0]}我将上述脚本取名为 foo,将其置于我定义的一个专用于寄存脚本的目录内,并赋其可执行权限,而后执行 foo, $ foo/home/garfileo/.my-scripts/foo换一种形式执行 foo, $ bash /home/garfileo/.my-scripts/foo/home/garfileo/.my-scripts/foo再换一种形式, $ bash $(foo)/home/garfileo/.my-scripts/foo再换一种形式执行 foo, $ source $(foo)/home/garfileo/.my-scripts/foo在 Bash 的语法里,$(...) 称为命令替换,即开拓一个子 Shell,在其中执行括号内的命令,并以文本的模式返回后果。 上述试验揭示的是,foo 在运行时,能够确定本人身处何处。foo 能够,我也能够。 BASH_SOURCE[0] 与 $0 有什么区别?我想了起来,仿佛通过 $0 也能令一个脚本获知本身所处地位。将 foo 改为 #!/bin/bashecho ${BASH_SOURCE[0]}从新做一遍试验: $ foo/home/garfileo/.my-scripts/foo$ bash $(foo)/home/garfileo/.my-scripts/foo$ source $(foo)bash仅仅是在应用 source 执行 foo 时,失去的后果不是 foo 脚本的地位,而是 bash。 ...

March 11, 2021 · 2 min · jiezi

关于bash:Gar

Gar 是一个 Bash 脚本程序,用于治理 Markdown 文档我的项目,可将 Markdown 文档汇合其转化为 HTML 文档汇合。Gar 的运行,依赖 pandoc,git,tree 以及一个能在 Shell(命令行)里关上指定网页文件的网页浏览器。Gar 默认将 Firefox 作为网页浏览器,然而可在文档我的项目根目录的 gar.conf 文件中指定其它符合要求的网页浏览器。 文档我的项目初始化命令:gar init 文档我的项目名 例如: $ gar init demo[master (root-commit) 6f7dd1c] init 1 file changed, 2 insertions(+) create mode 100644 gar.conf以下命令可察看 gar init 发明了什么: $ cd demo$ ls -a .. gar.conf .git output source 图片$ gar treedemo├── gar.conf├── output├── source└── 图片$ git logcommit 6f7dd1c23c0cc8b18eb84b6a5236605eebd2bfbf (HEAD -> master)Author: xxx <xxx@yyy.zzz>Date: Tue Mar 9 07:30:53 2021 +0800 init文档我的项目初始化后,文档的撰写和编辑工作次要在 source 目录进行。Gar 将 Markdown 文档转化为 HTML 文档后,放在 output 子目录内。 ...

March 9, 2021 · 6 min · jiezi

关于bash:屏幕录制

在「用 Bash 脚本写一个截屏工具」一文的后记里,我将截屏脚本略加改变,失去了一个可录制屏幕中指定窗口区域的脚本。翻手为云,覆手为雨,全拜 ffmpeg 所赐。 我毫无新意地将这个录制屏幕的脚本取名为 recorder,内容为: #!/bin/bashNAME=$(date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g")RECORD=/tmp/${NAME}.mkvOUTPUT=/tmp/output-${NAME}.mkv# 获取屏幕分辨率SCREEN=$(xrandr | grep -o "current [0-9]* x [0-9]*" | sed -e 's/current *//g')SCREEN_W=$(echo $SCREEN | sed -e 's/ x [0-9]*//')SCREEN_H=$(echo $SCREEN | sed -e 's/[0-9]* x //')# 获取窗口几何参数declare -a WIN_PARAMSWIN_PARAMS=($(xwininfo | sed -n -e '/^[[:space:]]*Absolute ..*[XY]/p; /^[[:space:]]*Relative ..*[XY]/p; /^[[:space:]]*Width:/p; /^[[:space:]]*Height:/p' | awk 'BEGIN{FS=":"}{print $2}'))# 结构现实中的截图区。# 之所以如此,与 xwininfo 输入的窗口左上角绝对坐标无关。MARGIN=${WIN_PARAMS[2]}WIN_X=$((${WIN_PARAMS[0]} - ${WIN_PARAMS[2]}))WIN_Y=$((${WIN_PARAMS[1]} - ${WIN_PARAMS[3]}))WIN_W=$((${WIN_PARAMS[4]} + ${WIN_PARAMS[2]} + $MARGIN))WIN_H=$((${WIN_PARAMS[5]} + ${WIN_PARAMS[3]} + $MARGIN))# 截图区越界解决if (($WIN_X < 0)); then WIN_X=0; fiif (($WIN_Y < 0)); then WIN_Y=0; fiif (($WIN_W + $WIN_X > $SCREEN_W)); then WIN_W=$(($SCREEN_W - $WIN_X)); fiif (($WIN_H + $WIN_Y > $SCREEN_H)); then WIN_H=$(($SCREEN_H - $WIN_Y)); fi# 录制指定窗口区域ffmpeg -video_size ${WIN_W}x${WIN_H} \ -framerate 30 -f x11grab \ -i :0.0+${WIN_X},${WIN_Y} \ -c:v libx264rgb -crf 0 -preset ultrafast $RECORD# 视频后处理ffmpeg -i $RECORD -c:v libx264rgb -crf 0 -preset veryslow $OUTPUT而后有些可惜,感觉没法像截屏脚本那样,能够绑定到桌面环境的某个快捷键上。起因是,ffmpeg 在录制屏幕时,须要我在其运行的命令行窗口里摁一下 q 键方能完结屏幕录制。过后感觉,无解。截屏脚本在抓到屏幕画面后会主动退出,所以不存在这样的问题。 ...

February 22, 2021 · 1 min · jiezi

关于bash:用-Bash-脚本写一个截屏工具

太极拳能不能打?学会了少林七十二绝艺,就能打,否则……不能打。拳理与编程,是相通的。白俄女芭蕾舞者的肌肉运用之妙,也是与拳理相通的。 在 Bash 看来,或者在任意一种在 Linux 环境里称得上 Shell 的物种看来,只有有了 ffmpeg、grep、sed、awk 以及一个 X 窗口零碎之类的货色,就能够用不到 50 行代码写出一个不错的截屏工具。当然,假使还有 GIMP 或相似的货色,风味更盛。 工夫的名义晓得上面这条命令,在我敲了回车键之后,会输入什么吗? $ date +"%Y-%m-%d %T"会输入 2021-02-19 23:32:18若以这样的后果作为截屏所得图像的文件名,是不是很好?反正我是打算这样做,先将工夫里的 : 变成短横: $ date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g"工夫在流逝: 2021-02-19-23-34-47将上述代码整合起来,利用子 Shell,就有了截屏脚本 screenshot 的第一步……师出有名: #!/bin/bashNAME=$(date +"%Y-%m-%d %T" | sed -e "s/ /-/g; s/:/-/g")IMAGE=/tmp/${NAME}.pngecho $IMAGE为 screenshot 增加可执行权限: $ chmod +x screenshot而后将其放到零碎 PATH 变量所指定的目录内,执行这个脚本,就能够失去截屏后果的文件名: $ screenshot/tmp/2021-02-19-23-40-11.png工夫在流逝…… 世界有多大?世界有多大呢……单就截屏而言,世界就是计算机屏幕分辨率那么大。 X 窗口零碎有个工具叫 xrandr,它会通知我世界有多大: $ xrandrScreen 0: minimum 8 x 8, current 1600 x 900, maximum 32767 x 32767LVDS1 connected primary 1600x900+0+0 (normal left inverted right x axis y axis) 310mm x 170mm 1600x900 60.01*+ 59.82 40.00 1400x900 59.96 59.88 1368x768 60.00 59.88 59.85 1280x800 59.81 59.91 1280x720 59.86 60.00 59.74 1024x768 60.00 1024x576 60.00 59.90 59.82 960x540 60.00 59.63 59.82 800x600 60.32 56.25 864x486 60.00 59.92 59.57 800x450 60.00 640x480 59.94 720x405 59.51 60.00 58.99 640x360 59.84 59.32 60.00 VGA1 disconnected (normal left inverted right x axis y axis)VIRTUAL1 disconnected (normal left inverted right x axis y axis)然而,我感觉它的废话太多了。它所说的,只有第一句是我想晓得的: ...

February 20, 2021 · 3 min · jiezi

关于bash:Bash-常用命令记录

1. 递归查找某目录下有具体后缀的文件,并cp到指定目录find $SEARCHING_DIR -name "*.wav" -exec cp {} $TAR_DIR \;若只查找文件,前面可加上-type f 2. 查看某个文件行数wc -l $TAR_DIR 3. 查看目以后录下的文件数量(蕴含子目录的文件)ls -lR|grep "_"|wc -l 4. 查看当前目录下文件夹的数量(不蕴含子目录的文件夹)ls -l|grep "d"|wc -l蕴含与否取决于 ls后加不加-R参数,文件夹还是文件取决于grep后的”d“与否。 5. 查看当前目录下以test结尾的文件的数量(蕴含子目录中的文件)ls -lR test*|grep "_"|wc -l 6. 查看当前目录下各个文件夹所占物理空间du -sh * 7. 查看以后已挂载磁盘占用空间df -lh 继续更新~

December 14, 2020 · 1 min · jiezi

如何写一个命令行的秒表

序言相信各位读者对秒表都不陌生,智能手机上通常都有这样一款软件 有一天心血来潮,便想要“复刻”一个命令行版本的秒表程序——主要是想尝试一下新学会的、“原地更新”的技能,而不是一行接一行地输出。程序的运行效果如下 那么这是怎么做的呢? 实现思路及代码如何获取流逝的时间长度?要实现一个秒表,首先要知道从开始计时至今过了多久。在*nix系统中,表示时刻的事实标准是Epoch Time,在shell脚本中要获取Epoch Time可以用date命令。再用首尾时刻相减便得到了期间流逝的秒数了,示例代码如下 begin_at=$(date '+%s')# 睡个觉end_at=$(date '+%s')((interval=${end_at} - ${begin_at}))双圆括号是一种在shell脚本中执行算术运算的语法,其它语法可以参见Math in Shell Scripts。 如何换算为时分秒?有了interval中存储的总秒数后,换算成时分秒便是轻而易举的事情,示例代码如下 ((hours=${interval} / 3600))((minutes=(${interval} % 3600) / 60))((seconds=(${interval} % 3600) % 60))如何输出形如hh:mm:ss的格式?hh:mm:ss的意思是分别用两个十进制数字显示时分秒,并以冒号分隔它们。如果有任何一个单位的数值小于10,便用字符0填充左侧的空白。按这个格式,凌晨1点2分3秒便会显示为01:02:03。 要在命令行中打印字符串,最容易想到的便是echo命令,只可惜它不能方便地实现填充字符0的需求。 强人所难也不是不行,示例代码如下 hours=1minutes=2seconds=3if [ "${hours}" -lt '10' ];then echo -n "0${hours}"else echo -n "${hours}"fiecho -n ':'if [ "${minutes}" -lt '10' ];then echo -n "0${minutes}"else echo -n "${minutes}"fiecho -n ':'if [ "${seconds}" -lt '10' ];then echo -n "0${seconds}"else echo -n "${seconds}"fi更优雅的方法是用printf命令来自动填充左侧的字符0 ...

July 3, 2020 · 1 min · jiezi

shell-脚本学习答疑

继上篇 shell 入门文章, 该篇文章会列举出我在学习 shell 的过程中, 遇到的一些疑问, 然后自己查资料以后的解答. 如何运行 shell 脚本回顾例子: [root@host shell]# vim start[root@host shell]# cat start#!/bin/bashecho 'hello'[root@host shell]# chmod 755 start[root@host shell]# ./starthello[root@host shell]#执行时, 为什么要加 ./ ?这要从系统的 $PATH 变量说起, 当我们执行例如 ls, pwd 等指令时, linux 并不会在整个系统翻箱倒柜搜索 ls, pwd 程序, 而是会从 $PATH 变量定义的目录列表下查找, 如果找不到, 就会报 command not found 的错误.因为我们的脚本不在 $PATH 变量目录列表下, 所以需要加上 ./ 指示该应用程序处于当前目录下.另外, 如果我们不想加 ./, 可以将该目录作为系统搜寻指令的其中一个目录: [root@host shell]# vim ~/.bash_profile# 修改 .bash_profile, 添加你的脚本目录.# $PATH 变量定义中, 多个目录用冒号 : 分隔export PATH="$PATH":~/shell# 让 shell 重新读取配置文件[root@host shell]# source ~/.bash_profile[root@host shell]# starthello第一行 #!/bin/bash 有什么作用 ?告诉操作系统用什么解释器执行该脚本 ...

November 2, 2019 · 3 min · jiezi

基于bash-shell脚本制定灵活的trimlog日志清理策略

前言透过Zabbix文件系统监控告警发现日志未定时清理,进一步排查后确认前同事写的trimlog脚本在某些服务器上执行定时任务时出现了异常,review后发现代码逻辑本身没有问题,主要由于cron job在执行date参数时没有使用转义字符导致,不过trimlog.sh这个日志清理脚本代码结构清晰易读,做下共享方便大家参考和二次修改。 基于bash shell脚本制定灵活的trimlog日志清理策略更新历史2019年10月08日 - 初稿 阅读原文 - https://wsgzao.github.io/post... 扩展阅读 基于bash find命令执行log日志备份和清理的一次简单实践 问题描述# 检查crontabcat /etc/crontab#Ansible: gdp_trim_log0 1 * * * root /data/release/trimlog.sh '/data/release/log/gop_gdp' 30 1 '*' '%Y%m%d' >> /data/release/trimlog.txt0 1 * * * root /data/release/trimlog.sh '/data/release/log/cacheupdate' 30 1 '*' '%Y%m%d' >> /data/release/trimlog.txt#Ansible: gdp_trim_backup0 1 * * * root /data/release/trimbackup.sh '/data/release/backup/gop_gdp' 30 >> /data/release/trimbackup.txt# 检查crontab日志less /var/log/cronOct 8 01:00:01 sg-gop-10-71-12-146 CROND[183782]: (root) CMD (/data/release/trimlog.sh '/data/release/log/gop_gdp' 30 1 '*' ')Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183783]: (root) CMD (/data/release/trimbackup.sh '/data/release/backup/gop_gdp' 30 >> /data/release/trimbackup.txt)Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183784]: (root) CMD (/data/release/trimlog.sh '/data/release/log/cacheupdate' 30 1 '*' ')Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183786]: (root) CMD (/usr/lib64/sa/sa1 1 1)Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183788]: (root) CMD (cd /tmp && ss -tna | awk '{print $1}' | sort | uniq -c > snmpss.tmp && chcon system_u:object_r:snmpd_var_run_t:s0 snmpss.tmp && mv -f snmpss.tmp snmpss.cache)Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183779]: (root) CMDOUT (/bin/bash: -c: line 0: unexpected EOF while looking for matching `'')Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183779]: (root) CMDOUT (/bin/bash: -c: line 1: syntax error: unexpected end of file)Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183778]: (root) CMDOUT (/bin/bash: -c: line 0: unexpected EOF while looking for matching `'')Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183778]: (root) CMDOUT (/bin/bash: -c: line 1: syntax error: unexpected end of file)Oct 8 01:00:01 sg-gop-10-71-12-146 CROND[183777]: (root) CMDOUT (find: ‘/data/release/backup/gop_gdp’: No such file or directory)# 在crontab的date中加入转移字符即可0 1 * * * root /data/release/trimlog.sh '/data/release/log/gop_gdp' 30 1 '*' '\%Y\%m\%d' >> /data/release/trimlog.txt0 1 * * * root /data/release/trimlog.sh '/data/release/log/cacheupdate' 30 1 '*' '\%Y\%m\%d' >> /data/release/trimlog.txtbash shell代码vim /data/release/trimlog.sh#!/bin/bashdir=`dirname $0`if [ $# -lt 1 ]then echo "usage: $0 DIRECTORY [DELETE_DAYS] [COMPRESS_DAYS] [COMPRESS_FILE_PATTERN] [DATE_PATTERN]" echo "multiple matches example for COMPRESS_FILE_PATTERN: data|error" exitficurrent_date=$(date +"%Y-%m-%d %T")echo "Start trim log at $current_date "path=$1if [ $# -gt 1 ]then log_delete_days=$2else log_delete_days=7fiif [ $# -gt 2 ]then log_compress_days=$3else log_compress_days=0fiif [ $# -gt 3 ]then log_compress_match=$4else log_compress_match='*'fiif [ $# -gt 4 ]then date_pattern=$5else date_pattern="%Y-%m-%d"fiif [ $log_delete_days -gt 0 ]then files=`ls $path/*.log.*` i=0 while [ $i -le $log_delete_days ] do d=`date "+$date_pattern" --date "$i day ago"` files=`echo "$files"|grep -v $d` i=`expr $i + 1` done echo "$files"|xargs rm -ffiif [ $log_compress_days -gt 0 ] && [ -n "$log_compress_match" ]then files=`ls $path/*.log.*|grep -v "\.gz$"|grep -E "$log_compress_match"` i=0 while [ $i -lt $log_compress_days ] do d=`date "+$date_pattern" --date "$i day ago"` files=`echo "$files"|grep -v $d` i=`expr $i + 1` done echo "$files"|xargs gzipficurrent_date=$(date +"%Y-%m-%d %T")echo "Finish trim log at $current_date "vim /data/release/trimbackup.sh#!/bin/bashdir=`dirname $0`if [ $# -lt 1 ]then echo "usage: $0 DIRECTORY [KEEP_BACKUP_NUM]" exitficurrent_date=$(date +"%Y-%m-%d %T")echo "Start trim backup at $current_date "path=$1if [ $# -gt 1 ]then keep_backup_num=$2else keep_backup_num=10fii=0find $path -maxdepth 1 ! -path $path -type d -printf '%p\n' | sort -r -n | while read dir; do i=`expr $i + 1` if [ $i -gt $keep_backup_num ] then echo "del $dir" rm -rf $dir else echo "keep $dir" fidonecurrent_date=$(date +"%Y-%m-%d %T")echo "Finish trim backup at $current_date "

October 8, 2019 · 3 min · jiezi

LINUX-shell-脚本讲解

注释一个常用的shell脚本,理解了里面用的语法和命令,就可以自己去写shell脚本了。\#!/bin/bash\# 功能:拷贝远程机器的model文件到本机set -u # 遇到不存在的变量就会报错,并停止执行set -x # 进入跟踪方式,显示所执行的每一条命令work_dir=$(readlink -f $(dirname $0)) # 获取脚本所在的目录,并赋值给变量,$0 表示 当前脚本的文件名 if [ $# -eq 1 ]; then # shell的if语句,括号内的空格不能少,$#表示传递给脚本或函数的参数个数,-eq 判断两个数字是相等 date=$1 # $1 表示传递给脚本表示第一个参数 env="prod" # 变量的赋值elif [ $# -eq 2 ]; then date=$1 env=$2 # $2 表示传递给脚本表示第二个参数else date=$(date -d "1 day ago" +"%Y-%m-%d 00:00:00") # 这里的$(command)是固定用法,将command的结果赋值给date这个变量 env="prod"fidingtalk_webhook="https://oapi.dingtalk.com/robot/send?access_token=some_token"function dingtalk_request(){ # 在shell中定义一个函数 curl -H "Content-type: application/json" -X POST -d '{"msgtype": "text","text": {"content": "'$message'"}}' $dingtalk_webhook}local_path="/path/to/local"date_suffix=$(date -d "$date" +"%Y%m%d") # 这里三个变量都是通过命令获取到具体的值date_now=$(date +"%Y-%m-%d_%H:%M:%S")host_name=$(hostname)remote_host="hostname_test"remote_path="/path/to/remote_test"if [ $env = "prod" ]; then # shell 中字符串比较是否相等用 = remote_host="hostname_prod" remote_path="/path/to/remote_prod"fitest -e $local_path/copy_tag/done.$date_suffix # test -e 命令来查看文件是否存在,$date_suffix表示获取date_suffix这个变量的值if [ $? -eq 0 ]; then # $?表示上一条命令(即test -e,文件存在返回值为0)的返回值;如果本地有done文件,说明已经拷贝过了,就不用重复拷贝 echo "$local_path/copy_tag/done.$date_suffix exist" # echo 向屏幕打印一条信息 exit 0 # shell 脚本正常退出,返回值是0 (脚本执行完,0可以通过$?获取,即上一条命令的返回值)fissh $remote_host "test -e $remote_path/model.$date_suffix" # 使用ssh命令到远程机器上执行test命令if [ $? -ne 0 ]; then # -ne 用来判断数字不相等;远程机器的model文件还没有生成,退出 echo "$remote_path/model.$date_suffix not exist" exit -1 # shell 脚本异常退出,返回值是-1fifile_flag=$(head $local_path/flag) # head 命令打印文件中内容(内容只用一行,0或者1),并赋值给file_flagcurrent_file_flag="0"if [ $file_flag -eq 0 ]; then current_file_flag="1"elif [ $file_flag -eq 1 ]; then current_file_flag="0"else message="[$host_name]Discp_reomte_model_failed:Get_invalid_flag_$file_flag[$date_now]" # 将多个变量和一些信息拼接成字符串 echo $message dingtalk_request # 调用之前定义的函数 exit -1fiscp $remote_host:$remote_path/model.$date_suffix $local_path/$current_file_flag/modelFile # 将远程机器的文件拷贝到本地if [ $? -ne 0 ]; then # 拷贝失败,即返回值不为0,就发送钉钉报警,并退出 message="[$host_name]Discp_reomte_model_failed:$remote_path/model.$date_suffix[$date_now]" echo $message dingtalk_request exit -1fiecho "$current_file_flag" > $local_path/flag # 将信息(current_file_flag的值)重定向到指定文件中,即shell的写文件echo "$current_file_flag" > $local_path/copy_tag/model.done.$date_suffixmd5sum $local_path/$current_file_flag/modelFile # md5sum 用来查看文件的md5值ssh $remote_host "md5sum $remote_path/model.$date_suffix" # 查看远程机器文件的md5值,这两条命令记录下md5,用来检查文件是否一致message="[$host_name]Discp_reomte_model_success_to_$local_path/$current_file_flag/modelFile[$date_now]" echo $message # 拷贝成功,打印信息dingtalk_request # 拷贝成功,发送钉钉信息

October 4, 2019 · 1 min · jiezi

LINUX-shell-常用命令和技巧总结datesortfind

时间(date)获取当前的时间戳 date +%s1570093753时间戳转化为可读时间 date -d @1570093753 "+%Y%m%d %H:%M:%S"20191003 18:09:13时间偏移计算 date -d "2019-10-03 30 days ago" "+%Y%m%d" 20190903获取当前是周几 date +%u # day of week (1..7); 1 is Mondaydate +%w # day of week (0..6); 0 is Sunday4其他更详细的用法 man date排序(sort)主要用于小文件排序(sort lines of text files) sort -t ',' -k 2 -nr ./file_name #次将文件用,分割,对第2列(从1开始算),按照数字顺序(numeric-sort)将整行内容降序排列文件快速去重 sort -u ./file_name其他更详细的用法 man sort 查找(find)找到文件夹中最大的文本 find /path/to/file -name "*txt" | xargs ls -li|sort -k 6|tail -n 1找到文件夹中30天前修改的文件并打印/删除 ...

October 3, 2019 · 1 min · jiezi

为brewgitpip设置代理为brew正确换源终极版

1.如何正确设置HTTP/HTTPS代理设置了brew通过socks5的代理后,会发现pip其实是不支持socks5的,只能通过http/https。 首先获取端口点击状态栏的小火箭HTTP Proxy Preference获取HTTP的端口号(我是1087)将以下内容添加进.bash_profile(bash用户)/.zshrc(zsh用户)并保存#设置HTTP/HTTPS Proxyexport http_proxy="http://127.0.0.1:1087"; export https_proxy="http://127.0.0.1:1087";保存,进入shell,以zsh为例 #更新配置source .zshrc#验证brew updatepip install --upgrade pip2.如何正确为brew换源设置全局代理或者换源都有各自的优缺点,如果你想换源,那么尝试下面的步骤,要注意的是,换源和设置代理只能选其一,不然结果是一样的。 按照一般方法更换中科大源后,执行brew update还是巨慢,后来发现是cask仍然接在github上,所以要把cask一起换了。要注意的是,Caskroom 的 Git 地址在 2018年5月25 日从 https://github.com/caskroom/h... 迁移到了https://github.com/Homebrew/h...。 更换中科大源:# 替换brew.git:cd "$(brew --repo)"git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git# 替换homebrew-core.git:cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"git remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git# 替换homebrew-cask.git:cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-caskgit remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git# 替换homebrew-bottles:echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.zshrcsource ~/.bash_profile重置官方源:#重置brew.git:cd "$(brew --repo)"git remote set-url origin https://github.com/Homebrew/brew.git#重置homebrew-core.git:cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"git remote set-url origin https://github.com/Homebrew/homebrew-core.git#重置homebrew-cask.git:cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-caskgit remote set-url origin https://github.com/Homebrew/homebrew-cask#Caskroom 的 Git 地址在 2018年5月25 日从 https://github.com/caskroom/homebrew-cask 迁移到了 https://github.com/Homebrew/homebrew-cask #最后注释掉/.bash_profile里的homebrew-bottles并保存,以bash为例cd ~open .bash_profile#更新.bash_profilesource .bash_profile#验证brew update

July 15, 2019 · 1 min · jiezi

自动化执行npm-publishgit-commitgit-tagghpages全流程的shell

因为过程复杂和老忘记改版本号(╯‵□′)╯︵┻━┻,为简化自己提交开源工具而写的shell,记录一下。 用alias写在了.zshrc里,直接用 command [version] [commit/tag message] [subtreeDir]使用,[version]和[message]必须。 其中包含了自动修改版本号、git提交操作、tag操作、publish、提交gh-pages的subtree。 我的drag-block目前在用,前期准备工作如下: 在github上建库使用webpack打包,设置dev和prod环境build会把代码以及示例打包,分别放在lib/ 和 example/下需要一个npm账号在登录状态配置npm publish之前的ignore、files等建立gh-pages分支,并使用subtree将example/提交到该分支会得到: 一个github仓库github releases,可以用代码包的方式下载可以直接在npm install的工具一个github page,内容是你做的示例页面具体的可以从drag-block这里看。 #!/bin/sh if [ ! -n "$3" ];then subtreeDir="example/"else subtreeDir=$3fiif [ ! -n "$1" ];then echo '请输入版本号';else sed -i '' 's#\("version": "\).*#\1'"$1"'",#g' package.json # 修改package.json中的version npm run build git add . git commit -m "$2" git tag $1 -m "$2" git push git push --tags npm publish git subtree push --prefix=${subtreeDir} origin gh-pages # 使用subtree的方式提交我的 example/ 目录为gh-pages分支内容,用以github pages。fi

July 5, 2019 · 1 min · jiezi

高效SHELL环境-step-by-step一-命令别名

基础环境在进行高效的SHELL实践之前,首先配置一下基础环境,当然首先是需要一台MacOS电脑。这里采用: zsh + oh-my-zsh + zsh-completions + zsh-autosuggestions 。具体安装步骤如下: # 切换默认SHELL为 zsh$: chsh -s /bin/zsh# 安装 oh-my-zsh$: sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"# 安装 zsh-completions$: git clone https://github.com/zsh-users/zsh-completions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-completions# 安装 zsh-autosuggestions$: git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions安装完成后,轻松敲打几个命令,该提示的、补全的也都如预期般的展示,的确大大的提升了命令输入的效率。 命令别名减少输入的另一个办法就是对拼写复杂的命令设置简易的别名。alias别名命令最常规的用法就是,定义别名。当然这是alias命令的主要功能之一。不过它还具有其它功能,不细看的话很容易被忽略掉。 不妨通过tldr命令查询看看alias的功能列表: $: tldr aliasaliasCreates aliases -- words that are replaced by a command string.Aliases expire with the current shell session, unless they're defined in the shell's configuration file, e.g. `~/.bashrc`.- List all aliases: alias- Create a generic alias: alias word="command"- View the command associated to a given alias: alias word- Remove an aliased command: unalias word- Turn `rm` into an interactive command: alias rm="rm -i"- Create `la` as a shortcut for `ls -a`: alias la="ls -a"不难看出,alias命令还有另外检索的功能,该功能在我们设置别名时先判断是否已经存在别名非常有用。 ...

June 27, 2019 · 2 min · jiezi

linux-常用命令汇总

linux 常用命令汇总linux本质linux : 文件系统,一切皆文件命令、选项、参数彼此之间要用空格命令本质上就是一个程序文件,选项和参数本质上都是参数Linux中的大小写是敏感的,单词容易拼错,建议初学者使用快捷键TAB:对命令的自动补全 - 按一下自动补全,如果没有自动补全代表有多个候选项 - 按两下显示所有的匹配项最常见的Linux初学者的报错: command not found :命令没有找到 命令单词写错了语法格式不对No such file or directory:没有找到该文件或者目录 文件路径写的不对文件名写的不多绝对路径、相对路径相对路径: 如果不加 / ,即为从当前路径开始计算绝对路径: 绝对路径都是使用 / 打头linux基本命令1. ls = list 显示当前目录下所有的文件,包括目录ls /home 显示指定目录下的所有文件 -a 隐藏的文件也要显示出来 -l 显示详细的信息,包括属主,属组,文件大小,创建时间等 -al 显示所有的文件,并且显示详细的信息 2. cd = change directory 切换目录即打开目录目录结构/ 根目录 bin :基本命令的目录,所有用户都可以用的命令就在这里 sbin :超级管理员才能使用的命令,root用户使用的管理类命令 usr :共享软件的安装位置 var :日志目录,重要记录文件的目录 tmp :临时目录 opt :可选目录 lib :库包的目录 etc :Linux的配置文件目录 root :管理员的家目录,root用户的家目录 home :用于存储每个普通用户的家目录的,类似于windows上的用户目录 zhizuobiao 用户名 haitong 用户名 …… 3. 其他常用命令pwd = print word directory 显示当前的目录机构clear:清屏,等价于Ctrl+l ...

June 27, 2019 · 2 min · jiezi

安卓猿的ADB骚操作

前言当前PC连着多个安卓设备时,当超出了 adb 所支持的设备数,步骤如下: adb devices 列出你当前的设备列表,然后拷贝你要安装的设备Device Id; 使用 adb -s deviceIdinstall .... 来进行 APK 安装error: more than one device/emulatoradb: error: failed to get feature set: more than one device/emulator- waiting for device -error: more than one device/emulator效果图 #adb 快捷命令function adbConnect(){ echo "Your connect is 192.168.$1" # 这个 $1 必须要参考底下命令的下达 adb connect 192.168.$1}function adbDisconnect(){ adb disconnect}function adbShell(){ adb shell}function devices(){ adb devices}# 获取安卓设备数量function getAdbDevicesCount(){ no_dev=2 #没有设备的总行数 第一行: List of devices attached 第二行 空 line="`adb devices | wc -l`" # echo "$line" echo $(($line-$no_dev))}#获取自定义格式设备名称 参数1: adb devices 设备ID indexfunction getFmtDeviceName(){ if [ -n "$1" ]; then line=$1 let "line++" #跳过1行 deviceId="`adb devices | sed -n "${line}p" | awk '{printf $1"\n"}' `" # name="`adb devices | sed -n "2p;${line}p" | awk '{printf NR ". " $1"\n"}' `" #简单列出设备ID manufacturer="`adb -s $deviceId shell getprop ro.product.manufacturer`" model="`adb -s $deviceId shell getprop ro.product.model`" version="`adb -s $deviceId shell getprop ro.build.version.release`" sdk="`adb -s $deviceId shell getprop ro.build.version.sdk`" name="${manufacturer} ${model} Android ${version} API ${sdk} Serial: ${deviceId} " # 去除某些设备后面携带回车符 name=`echo ${name} | tr -d '\r'` echo ${name} else echo "requires an argument" fi}# 列出所有设备function listFmtDevices(){ count=`getAdbDevicesCount` index=1 while(( $index<=count )) do name=`getFmtDeviceName ${index}` echo "${index}. ${name} " let "index++" done}# 获取设备ID 参数1: adb devices 设备ID indexfunction getFmtDeviceId(){ if [ -n "$1" ]; then line=$1 let "line++" #跳过1行 deviceId="`adb devices | sed -n "${line}p" | awk '{printf $1"\n"}' `" # 去除某些设备后面携带回车符 deviceId=`echo ${deviceId} | tr -d '\r'` echo ${deviceId} else echo "requires an argument" fi}# 安装apkfunction apk(){ if [ -n "$1" ]; then count=`getAdbDevicesCount` one_dev=1 if [ $count -eq $one_dev ] then # 单设备 name=`getFmtDeviceName 1` echo "install apk to devices: ${name}" adb install -r $1 elif [ $count -gt $one_dev ] then # 多设备 if [ -n "$2" ]; then # 带设备index index=$2 deviceId=`getFmtDeviceId ${index}` name=`getFmtDeviceName ${index}` echo "install apk to devices: $name" adb -s $deviceId install -r $1 else # 带apk文件路径 echo "install apk to which devices?" listFmtDevices read -p "Enter: " index apk $1 $index fi else echo "no devices" fi else echo "apk requires an apkPath argument" fi}# 卸载apkfunction uapk(){ if [ -n "$1" ]; then count=`getAdbDevicesCount` one_dev=1 if [ $count -eq $one_dev ] then # 单设备 name=`getFmtDeviceName 1` echo "uninstall apk on $name" adb uninstall $1 elif [ $count -gt $one_dev ] then # 多设备 if [ -n "$2" ]; then # 带设备index index=$2 deviceId=`getFmtDeviceId ${index}` name=`getFmtDeviceName ${index}` echo "uninstall apk on devices: $name" adb -s $deviceId uninstall $1 else # 带apk文件路径 echo "uninstall apk on which devices?" listFmtDevices read -p "Enter: " index uapk $1 $index fi else echo "no devices" fi else echo "uapk requires an pkg argument" fi}# 进入adb shell 环境function as(){ count=`getAdbDevicesCount` one_dev=1 if [ $count -eq $one_dev ] then # 单设备 name=`getFmtDeviceName 1` echo "${name} Last login: `date`" adb shell elif [ $count -gt $one_dev ] then # 多设备 if [ -n "$1" ]; then # 带设备index index=$1 deviceId=`getFmtDeviceId ${index}` name=`getFmtDeviceName ${index}` echo "${name} Last login: `date`" adb -s $deviceId shell else # 不带设备index echo "enter shell which devices?" listFmtDevices read -p "Enter: " index as $index fi else echo "no devices" fi}

June 15, 2019 · 3 min · jiezi

实现简单的监控脚本Bash的执行和异常捕获

当我们需要监控服务运行状态时,一般的策略是写定时脚本,定时执行探测服务状态,如果出现预期外情况,就报警。那么第一步我们就需要学会写一个监控脚本,这里我们会讲到bash的执行环境和异常捕获,以及一些简单的全局参数。 示例先看一段shell代码,这个监控脚本会时刻监控我们的mysql进程是否正常服务,每2分钟执行一次: #!/bin/bash#设置异常的捕获和退出set -eset -o pipefailset -u#获取当前脚本执行的命令和路径#self_name=`readlink -f $0`#self_path=`dirname $self_name`set +e# 脚本主体mysql_process_num=`ps aux | grep mysql | grep -v grep | grep -v bash | wc -l`set -e# 判断脚本输出,此处0为异常if [ "$mysql_process_num" -ge 1 ];then echo "$mysql_process_num|proc_name=mysql"else echo "0|proc_name=mysql"fi脚本命令解析执行器#!/bin/bash首行表示此脚本使用/bin/sh来解释执行,#!是特殊的标识符,后跟此脚本解释器的路径。类似的还有/bin/sh, /bin/perl, /bin/awk等。 我们在使用bash执行脚本的时候,会创建一个新的Shell,这个Shell就是脚本的执行环境,并默认提供这个环境的各个参数。 异常捕获set -eset -o pipefailset -uset +e我们的Shell会给脚本提供默认的环境参数,但是我们也可以用set命令来修改运行参数。在官方手册里一共有十几个参数,我们介绍常用的四个参数。 如果我们直接在终端运行set,不带任何参数,会显示所有的环境变量和Shell函数。 开启和关闭参数我们常见的类似传参形式的set -e代表打开e代表的环境参数,相反的set +e代表关闭e代表的环境参数。 捕获单行异常当我们遇到一个异常,如操作不存在的变量或者一行指令执行出错(行指令返回值不为0),Bash会默认输出错误信息,然后忽略这行错误,继续执行。这在大部分场景下并不是开发者想要的行为,也不利于脚本的安全和Debug。我们应该在错误出现的时候输出错误信息并中断执行。这样能够防止错误被累计和放大。 # 可执行文件run#!/bin/bash# 调用未定义的命令fooecho bar# 执行该文件$ ./run./run: line 3: foo: command not foundbar可以看到输出了错误信息,并继续执行。 如果我们想保证单行如果出现错误,就中断执行脚本,可以有三种写法: # 方法一command || exit 1# 方法二if ! command; then exit 1; fi# 方法三commandif [ "$?" -ne 0 ]; then exit 1; fi上面的方法统一为判断一行指令返回值是否为0来判断异常。类似的,如果我们的多个命令有依赖关系,即后者的执行需要前者成功,则需要写: ...

June 11, 2019 · 2 min · jiezi

业务流程图软件word流程图制作

业务流程图是涉及到企业很多部门的,研发部、生产部、销售部、采购部、财务部等等,必须要考虑到业务每个环节可能出现的所有问题。当然刚开始绘制的时候,我们只需要单向思维去判断。 最开始画流程图使用的就是Word,Word的操作复杂程度就差没地球上的人都知道了。我们只能画word里有的那种流程图,连接线也是固定的,点击图框可以复制粘贴,但是连接线就很麻烦了,不少人反应怎么做一幅流程图就这么麻烦呢?Visio一发布,结合种种当时人们在使用Word绘图时的痛点,吸引了一大批职场粉。Visio使用起来是很流畅的,价格人民币2000多,吓坏了不少不用绘图的吃瓜群众。好像国民脱离了Office就不能办公了一样,尤其是在很多日常用到Linux,Mac系统的人们开始烦恼,似乎就没有一款软件类似Visio,一款软件就能可以解决所有问题。 在这种背景下,亿图图示出现了。当下受很多人欢迎的绘图软件亿图绘图专家,这款神奇之处在哪里,在这里我给大家介绍一下。 下面是出自设计师们绘制的智能选择颜色模板 绘图小白可以访问亿图软件的动态帮助,点开它,你能找到亿图的产品研发团队准备的软件说明介绍,以及详细的图文、视频教程,让你可以更轻松、更快的熟悉软件,开始绘制你的业务流程图。 不少用户使用亿图绘制一份业务流程图时发现,亿图的功能是符合办公工具在用户心中位置的,可以用来做很多演示要用的图,可以添加很多很难画的图形: 专业的形状是必不可少的,基本流程图形状里具备了所有绘制流程图时需要用的形状: 业务流程图用到的符号很多,能够满足用户这个需求的软件很少。 符号库里的图形是根据模拟真实场景设计的: 这款软件厉害之处是去掉了操作中的“繁文缛节”,简单直接的配合用户画图,但用户依然可以使用工具绘制自己想要的图,最大程度的贴合用户体验。 所有符号的颜色都具备商务、美观、整洁的视觉效果:

May 22, 2019 · 1 min · jiezi

解决问题npm全局安装后仍然提示找不到命令

惨案我正常使用命令安装express。 $ npm install -g express-generator然后bash就提示安装成功 /Users/majialun/.npm-global/bin/express -> /Users/majialun/.npm-global/lib/node_modules/express-generator/bin/express-cli.js+ express-generator@4.16.1updated 1 package in 2.123s然后运行 express,按道理这个时候应该让我创建项目了,但是系统提示: bash: express: command not found其实不光是express,我自己写的脚本,也出现这个问题,全局安装成功,但是,通过bash就是调用不出来。 侦查先看看全局有哪些path变量 $ echo $PATH然后就出现了很多 /Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/Users/majialun/.rvm/bin:/Users/majialun/Documents/flutter/flutter/bin:/Users/majialun/.npm-global/bin/express:/Users/majialun/.rvm/bin:/Users/majialun/Documents/flutter/flutter/bin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/Users/majialun/.rvm/bin:/Users/majialun/Documents/flutter/flutter/bin:/Users/majialun/.npm-global/bin/express:/Users/majialun/.rvm/bin:/Users/majialun/.npm-global/bin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/Users/majialun/.rvm/bin:/Users/majialun/Documents/flutter/flutter/bin:/Users/majialun/.npm-global/bin/express:/Users/majialun/.rvm/bin:/Users/majialun/Documents/flutter/flutter/bin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/majialun/.rvm/gems/ruby-2.4.1/bin:/Users/majialun/.rvm/gems/ruby-2.4.1@global/bin:/Users/majialun/.rvm/rubies/ruby-2.4.1/bin:/Users/majialun/.rvm/bin:/Users/majialun/Documents/flutter/flutter/bin这个时候我们发现太多了,但是不要慌,刚才安装Express的时候,有一个提示显示了npm的全局脚本挂在哪个path下。 再看看安装成功的提示: /Users/majialun/.npm-global/bin/express -> /Users/majialun/.npm-global/lib/node_modules/express-generator/bin/express-cli.js+ express-generator@4.16.1updated 1 package in 2.123s我们可以看到,脚本安装在/Users/majialun/.npm-global/bin,这个目录里就是全部的全局脚本,注意是bin目录,express只是这个目录下的一个而已。 然后我们仔细在全局的path里找,发现并不存在这个路径……我也不知道我是怎么把路径搞没的,我在公司的Macbook Pro一切正常,但是随身带的Macbook就缺失路径。 破案我们直接去根目录(/Users/majialun/)下,显示隐藏文件后,看看有没有.bash_profile,没有就新建一个,有就直接修改,添加进去一个路径: export PATH=$PATH:/Users/majialun/.npm-global/bin:$PATH注意这个路径是从上面复制下来的,每个人的不一样,总之从安装成功的提示里,复制到/bin这里结束就可以了。好了之后,在终端里打source,然后把.bash_profile 拖进来,就有了: $ source /Users/majialun/.bash_profile 执行一下然后重启终端,跑起来试试看: majialun$ express warning: the default view engine will not be jade in future releases warning: use `--view=jade' or `--help' for additional optionsdestination is not empty, continue? [y/N] 没有not found 的报错,问题解决。 ...

May 16, 2019 · 1 min · jiezi

流程图循环画法流程图用什么办公软件

流程图(Flow Chart)是一种常见的工作图表。在企业中,流程图主要用于说明某一个过程,该过程可以是生产线上的工艺流程,也可用于表达完成任务所需的步骤。另外,流程图也常用于表示算法的思路,可以有效解决汇编语言和早期的BASIC语言环境中的逻辑问题。 运用流程图的时候,需要使用一些标准符号代表某些类型的动作。如用菱形框表示判定,用方框表示流程。具体的表示方法整理如下: 流程图的分类 流程图的种类多达10种,归纳整理如下: 但是根据使用的场景不同,大致可划分为7个类别,分别是商业流程图、跨职能流程图、数据流程图、事件管理流程图、IDEF图、工作流程图、SDL图。 商业流程图:又叫做业务流程图,是一种描述系统内部各人员与各单位的业务关系、管理信息以及作业顺序。它是一种物理模型,借助于此,分析人员可以找出业务流程中的不合理流向,方便优化。 跨职能流程图:可显示进程中各个步骤之间的相互关系,也能显示执行它们的职能单位。跨职能流程图按照分布的方向不同,可以分为水平跨职能流程图和垂直跨职能流程图。当跨职能流程图用于UML的时候,又被叫作泳道图。 数据流程图:一种描述系统数据流程的工具,可以将抽象的数据独立出来,通过特定的图形符号来展示信息的来龙去脉和实际流程。这是一种能描绘信息系统逻辑模型的重要工具。 事件管理流程图:这是IT服务管理中重要的流程,当一个事件被输入的时候,服务台的操作人员会依据事件的影响范围和紧急程度,对其进行初步的归类评估。 IDEF图:IDEF,即集成计算机辅助制造,一种用于描述企业内部运作的一套建模方法。IDEF图是用于表达这种建模方法的图示。 工作流程图:通过适当的符号来记录工作事项,能够反映一个组织系统中各项工作之间的逻辑关系。工作流程图可以帮助管理者了解实际工作活动,并去除工作中多余的工作环节,进而提升工作效率。 SDL图:使用说明和规范的语言(SDL)为通信、电信系统以及网络创建图表。 流程图的画法 了解流程图的类别后,那又该如何绘制我们所需的流程图呢?下面我们以亿图图示软件为例,介绍如何快速创建专业的流程图。 第1步:打开软件,“新建”-“流程图”,然后根据自己的需求,选择绘图模板。比如选择基础流程图,双击鼠标即可打开绘图面板。值得一提的是,亿图图示里除了模板,还有对应的例子,如果是新手绘图,可以借鉴流程图例子帮助自己加深认识。 第2步:从左侧符号库里选择所需的图形符号,并拖动至画布中。并依次添加,直至完毕。 第3步:使用连接线符号,对各个图形符号进行连接。亿图图示软件的连线十分便捷,鼠标点击需要连线的两端,即可生成直角连线。如果连线的位置不满意,也可以通过鼠标拖动线条的方式进行修改。 第4步:最后再添加文字和注释,一份完整的流程图即可大功告成。另外,亿图软件还有丰富的背景图案以及标题栏样式可以选择,这将大大提升流程图的颜值。 特别注意: 亿图图示软件拥有一键更换样式的功能,无需自己搭配,方便省事。 绘制完成的作品,可以导出图片、Html、Word、PPT、Excel等多种格式;也可以将作品保存在亿图云,不担心丢失的问题。 软件还有打印功能,即见即所得的打印方式,让流程图的存在不再只是虚拟化。 支持跨平台操作系统,不论是window、Mac还是Linux,都可以安装使用亿图软件。 多类型的图形图表设计软件,不仅可以绘制流程图,还能绘制思维导图、组织架构图、户型图、信息图等260种图示。

May 15, 2019 · 1 min · jiezi

Namenode启动后挂掉连不上journalnode

使用start-dfs.sh启动HDFS后,jps查看到有namenode进程,但是过一会没了。查看日志,是连不上8485端口,8485配的是journalnode,也就是说连不上journalnode而挂掉的。 参考https://segmentfault.com/a/11...,ha的集群需要先启动journalnode,那么node再连接journalnode,所以再搭建hadoop ha时,先手动启动的journalnode没有问题。可是start-dfs.sh是先启动namenode,后启动journalnode,然后让namenode进程启动后去等待重试连接journalnode,所以如果有时候journalnode启动慢了,可能namenode就启动不了挂了,这就是为什么会出现jps中出现了namenode而一会儿又消失的原因。 找到原因了,就对症下药:(二选一)1.修改core-site.xml配置,让namenode多等一会儿,详见原文出处;2.手动启动挂掉的namenode

May 15, 2019 · 1 min · jiezi

HTTP学习笔记

一、WWW概念WWW(World Wide Web),主要包括三部分概念URI,俗称网址HTTP,两个电脑之间传输内容的协议HTML,超级文本,主要用来做页面跳转解释:URL 的作用是能让你访问一个页面,HTTP 的作用是让你能下载这个页面,HTML 的作用是让你能看懂这个页面。 URI 是什么概念:统一资源标识符(英语:Uniform Resource Identifier,缩写URI)URI 分为 URL 和 URN,我们一般使用 URL 作为网址。 URN是什么概念:统一资源名称(英语:Uniform Resource Name,缩写URN) URL是什么概念:统一资源定位符(英语:Uniform Resource Locator,缩写URL) DNS是什么网域名称系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53[1]。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。 二、请求与响应2.1http作用HTTP 的作用就是指导浏览器和服务器如何进行沟通。浏览器负责发起请求服务器在 80 端口接收请求服务器负责返回内容(响应)浏览器负责下载响应内容 2.2请求示例Git Bash命令行执行后,请求内容为:-s显示进度-v完整的显示请求和响应-H添加请求头 curl -s -v -H "Frank: xxx" -- "https://www.baidu.com"【GET请求内容】GET / HTTP/1.1Host: www.baidu.comUser-Agent: curl/7.54.0Accept: */*Frank: xxxcurl -X POST -s -v -H "Frank: xxx" -- "https://www.baidu.com"【POST请求内容】POST / HTTP/1.1Host: www.baidu.comUser-Agent: curl/7.54.0Accept: */*Frank: xxxcurl -X POST -d "1234567890" -s -v -H "Frank: xxx" -- "https://www.baidu.com"【POST带数据请求内容】POST / HTTP/1.1Host: www.baidu.comUser-Agent: curl/7.54.0Accept: */*Frank: xxxContent-Length: 10Content-Type: application/x-www-form-urlencoded//注意此处是空行12345678902.3请求内容格式示例 ...

May 5, 2019 · 2 min · jiezi

运行shell脚本时进程数量变多

写了一个很简单的脚本,用于统计memcache进程的数量: #!/bin/bashecho `ps aux | grep memcache | grep -v grep | wc -l`然而在执行时却遇到了问题: [work@ oss_memcache_status]$ pwd/home/work/cdn/monitor/ocelot-scripts/oss_memcache_status[work@ oss_memcache_status]$ ./run.sh1[work@ oss_memcache_status]$ ../oss_memcache_status/run.sh3这个原因是因为我们在执行shell脚本时,会通过子进程的方式来执行,因此统计数量比预期要多。解决方案为grep -v bash。 执行shell脚本的方式我们有三种常用的方式执行shell脚本: source run.sh: 会在当前进程下执行脚本,执行时的变量会保存下来。. run.sh: 和source方法基本一样,区别在于source不是POSIX要求的。./run.sh: 如果脚本以#!/bin/bash开头,会在单独的子进程中执行,执行完毕后变量不保存。否则和source一样。因此,在上面的脚本中,我们在执行时,因为是以#!/bin/bash开头,会在子进程中执行,我们改动一下脚本看都是哪些进程: #!/bin/bashecho `ps aux | grep memcache | grep -v grep`执行: work 24414 0.0 0.0 108116 1276 pts/0 S+ 15:31 0:00 /bin/bash ../oss_memcache_status/run.shwork 24415 0.0 0.0 108116 612 pts/0 S+ 15:31 0:00 /bin/bash ../oss_memcache_status/run.shwork 30558 0.0 0.0 371236 47096 ? Ssl 2016 15:14 /usr/local/bin/memcached -d -m 256 -u nobody -l localhost -p 11211我们通过结果,可以看出来,第一个进程和第二个进程的父进程相同,他们都属于当前终端启动的进程。前两个分别为执行run脚本的进程和调起的子进程(这两个什么区别,我也不太清楚),第三个为真正的进程。 ...

April 25, 2019 · 1 min · jiezi

Zookeeper启动失败(CentOS 7 防火墙)

zookeeper配置:1.zoo.cfg的server.id=host:2888:38882.zoo.cfg的dataDir3.在dataDir中创建myid文件,写入当前节点的id 正确配置下zookeeper启动失败,ssh免密登录也已配置,查看zookeeper.out java.net.NoRouteToHostException: No route to host可能是防火墙没有关。CentOS 7 关闭防火墙命令: systemctl start firewalld #启动systemctl stop firewalld #关闭systemctl status firewalld #查看状态systemctl disable firewalld #停用(开机不启动)systemctl enable firewalld #启用(开机自启动)

April 22, 2019 · 1 min · jiezi

Bash脚本编程之算数扩张

算数扩张(Arithmetic Expansion)。通过使用反引号,双括号和let命令可以将字符串转换为数字表达式。 使用反引号通常和expr结合使用: z=`expr $z + 3`使用双括号((...))或$((...))双括号中的变量引用符号$可以省略。 z=$(($z+3))# 等同于z=$((z+3)) ((n=$n+1))# 等同于((n=n+1))# 等同于((n+=1)) # 但(($n+=1))会报错使用let命令let命令中的变量引用符号$可以省略。使用引号允许在let表达式中使用空格符号: let z=z+3let "z = z + 3"

April 21, 2019 · 1 min · jiezi

Bash脚本编程之引用

引用的意思是用引号括起一个字符串,以保护字符串中的特殊字符不被shell或shell脚本重新解释或扩展: # 在通配和正则模式中拥有特殊含义的*号在引用中失去了特殊意义bash$ ls -l [Vv]*-rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT -rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh -rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.shbash$ ls -l '[Vv]*'ls: [Vv]*: No such file or directory但某些程序会重新解释或扩展引号括起的字符串中的特殊字符。比如某些场景下引号的用途是保护shell命令参数,但仍然允许调用程序扩展特殊字符: bash$ cat file1.txtfirst name in file1.txtbash$ cat file2.txtFirst name in file2.txtbash$ grep '[Ff]irst name' *.txtfile1.txt:first name in file1.txtfile2.txt:First name in file2.txt引用变量引用变量时,通常建议用双引号括起。这可以防止重新解释引用字符串中除$, `, \外的所有特殊字符。 使用双引号可以防止单词拆分,用双引号括起的参数即使包含空格也将视为一个整体: List="one two three"for a in $List # 空格拆分变量为多个部分do echo "$a"done# one# two# threeecho "---"for a in "$List" # 双引号括起变量视为一个整体do echo "$a"done# one two three一个更详细的例子: ...

April 21, 2019 · 1 min · jiezi

Bash脚本编程之subshell

(command1;command2;command3;…)会启动子shell。子shell可以访问父shell的变量,对父shell变量的改动只在子shell中有效;子shell中定义的变量是局部变量,外部不能访问:#!/bin/bash# subshell.shecho “We are outside the subshell.“echo “Subshell level OUTSIDE subshell = $BASH_SUBSHELL"echo; echoouter_variable=Outerglobal_variable=(echo “We are inside the subshell.“echo “Subshell level INSIDE subshell = $BASH_SUBSHELL"inner_variable=Innerglobal_variable="$inner_variable"echo “From inside subshell, "inner_variable" = $inner_variable"echo “From inside subshell, "outer" = $outer_variable”)echo; echoecho “We are outside the subshell.“echo “Subshell level OUTSIDE subshell = $BASH_SUBSHELL"echo “From main body of shell, "inner_variable" = $inner_variable”# $inner_variable will show as blank (uninitialized)#+ because variables defined in a subshell are “local variables”.echo “global_variable = “$global_variable"“echo# =======================================================================# Additionally …echo “—————–”; echovar=41 # Global variable.( let “var+=1”; echo “$var INSIDE subshell = $var” ) # 42echo “$var OUTSIDE subshell = $var” # 41# Variable operations inside a subshell, even to a GLOBAL variable#+ do not affect the value of the variable outside the subshell!exit 0在子shell中对目录的改变不会影响父shell:#!/bin/bash# allprofs.sh: Print all user profiles.FILE=.bashrc # File containing user profile.for home in awk -F: '{print $6}' /etc/passwddo [ -d “$home” ] || continue # If no home directory, go to next. [ -r “$home” ] || continue # If not readable, go to next. (cd $home; [ -e $FILE ] && cat $FILE)done# When script terminates, there is no need to ‘cd’ back to original directory,#+ because ‘cd $home’ takes place in a subshell.exit 0程序可以在不同的子shell中并行执行:#!/bin/bash# subshell.sh# 在后台运行以确保并行执行(ping -c 10 127.0.0.1 > /dev/null) &(ping -c 20 127.0.0.1 > /dev/null) &# 等同于:# ping -c 10 127.0.0.1 > /dev/null &# ping -c 20 127.0.0.1 > /dev/null &# 通过ps可以发现两条子命令都是当前脚本启动的子shell,拥有不同的进程ID# 直到子shell执行完成才执行后续命令waitecho “finished"I/O重定向到子shell使用管道操作符,例如ls -al | (command) ...

April 12, 2019 · 2 min · jiezi

shell中的单引号和双引号理解

问题描述: 最近在写shell脚本的时候,涉及到一个使用shell脚本发送json数据的问题,就是发送的json数据双引号不见了,导致数据格式不正确,收到了错误的响应。后来仔细查看了资料才发现自己之前对shell单引号和双引号的理解有一些问题,在此记录一些现象和结果。问题解析: 1.首先,我这边使用的是bash脚本,放一下bash脚本的手册地址; 2.然后我们看一下官方的手册里面是怎么介绍的: 2.1 单引号:Single Quotes:Enclosing characters in single quotes (‘’’) preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash. 翻译出来就是:用单引号(’’’)括起字符可以保留引号中每个字符的字面值。单引号之间可能不会出现单引号,即使前面有反斜杠也是如此。我的理解是单引号中的值都是会直接输出字符或这字符串的字面量,不会去解析各种变量或者其他的符号,而且必须是成对出现的。如果两个单引号之间有单引号,或者两个单引号之间有反斜杆的单引号都是不会结束的情况,必须等待新的单引号出现,让它们成对了才会结束。(这里的意思是bash的解释器会对单引号去解析,只有成对的时候才会结束,否则会一直等待,所以呢单引号对号都是成对的使用,虽然我也不知道单个的单引号有什么用)。下面举几个栗子用来解释一下刚才说的:可以看到前两张图,输入了三个单引号,或者两个单引号之间是一个带反斜杆的单引号。都会出现>的符号,意思是等待继续的输入。第三张图输入了单引号以后,出现了;号,表示结束了。说明解释器对单引号都是要成对的去解析。 2.2 双引号:Double QuotesEnclosing characters in double quotes (‘"’) preserves the literal value of all characters within the quotes, with the exception of ‘$’, ‘’, ‘\’, and, when history expansion is enabled, ‘!’. When the shell is in POSIX mode (see Bash POSIX Mode), the ‘!’ has no special meaning within double quotes, even when history expansion is enabled. The characters ‘$’ and ‘’ retain their special meaning within double quotes (see Shell Expansions). The backslash retains its special meaning only when followed by one of the following characters: ‘$’, ‘’, ‘"’, ‘\’, or newline. Within double quotes, backslashes that are followed by one of these characters are removed. Backslashes preceding characters without a special meaning are left unmodified. A double quote may be quoted within double quotes by preceding it with a backslash. If enabled, history expansion will be performed unless an ‘!’ appearing in double quotes is escaped using a backslash. The backslash preceding the ‘!’ is not removed.The special parameters ‘*’ and ‘@’ have special meaning when in double quotes (see Shell Parameter Expansion).大概意思是说:双引号中的信息会保留字面量,但是同时会对$,,,这些符号做出特殊的解析。就是双引号中的变量和转义,和函数操作可以被正常解析出来。这个比较好理解,接下来我们看下单引号和双引号使用的一些栗子,加深一下我们的理解。实例:直接上图:输出1,2,应该没有什么问题都是输出的字面量的字符串。输出3,4,就是展示了单引号和双引号的区别,单引号继续输出了字符串,而双引号输出了变量a的值。输出5,6呢,其实就是我遇到的问题,脚本中需要使用到日期的变量,并且放入到json的数据中。输出5如果直接使用单引号,肯定行不通,因为不解析变量。输出6呢,虽然最外层使用了双引号,内部可以解析变量,但是发现问题没有,变量外面是没有双引号的,而json的数据格式是{“key”:“value”}。也是不符合的,原因在于shell解释器分辨不出来双引号是在第几层,仅仅查到一堆双引号就把它们结为夫妻(一对对的双引号进行解析),所以输出6的解析过程是"’{“解析出’{,第二对双引号”:“解析出:,第三对双引号”&dollar;start_date"解析出start_date的值,依次类推。得出了’{startDay:2019-03-31 00:00:00,endDay:2019-03-31 23:59:59}’。输出7相当于就是正确的输出了json格式的数据,原理也很简单在输出6已经解释清楚。总结:1.__写shell脚本的时候,如果不需要解析里面的内容,就使用单引号,反之,双引号;__2.记住shell解析单引号和双引号的规则,是就近原则,遇到一对单/双引号,就会解析出其中的内容,而不是根据什么最外层,最内层这种层级关系去解析的,这点要记住。所以在输入json或者其他的格式的数据的时候,混合使用单/双引号的时候要注意使用的顺序,否则得到的结果并不是你预想的那样 ...

March 31, 2019 · 2 min · jiezi

CAD图纸格式的版本高了的话,怎么进行手机快速转换图纸版本?

CAD图纸格式的版本高了的话,怎么进行手机快速转换图纸版本?那大家在平时得设计绘图中应该也都会遇到过相关的小问题,我们在将图纸打开需要进行查看编辑的时候,CAD图纸要不就是打不开,或者显示不全,再就是文件破损,那这是什么原因?其实,随着编辑器的版本的不同,绘制完保存的图纸也是会产出不同的版本的,随意,不同的版本的CAD图纸要是在不适应的编辑器上进行查看编辑,就会产生格式不兼容的问题,那么怎么解决?下面就最简单的手机移动端给大家解决一下方法。步骤一:手机端搜索“迅捷CAD转换器”下载安装至手机桌面(目前该软件在“百度”“腾讯应用宝”“360”“搜狗”“阿里应用”“联想”“VIVO”“小米”“华为”等多平台已上线)用户可根据需求自行选择,然后下载安装到手机桌面上即可。步骤二:在手机桌面找到迅捷CAD转换器APP,轻触点击启动进入软件缓冲可操作界面。如图所示软件主要含有CAD版本转换、CAD转PDF、PDF转CAD、CAD转图片四大功能,这里我们选择“CAD版本转换”功能即可。步骤三:点击,快速进入“数据扫描中”状态。此时软件会自动为你检索出所有适合转换的CAD文件。我们可以点击选择要进行转换的图纸文件即可,或者点击全部文件,在指定的位置打开图纸即可。步骤四:手动选择需要进行转换的CAD图纸文件,点击右上角设置小图标按钮可进行文件参数设置。主要有“输出类型”“输出版本”板块设置。这里我们选择并点击“输出版本”按钮。在底部选择输出版本(DXF)及类型!然后点击保存直接跳转至转换界面。步骤五:然后点击“开始转换”按钮进入文件转换状态。完成后,即可对文件进行转换前后查看啦!那么以上就是简单的转换方法了,希望可以帮助的到大家哦!

March 28, 2019 · 1 min · jiezi

linux系统下用crontab定时清除日志文件

在应用服务器上,我们常常需要定时清理文件,尤其是日志,这时候就需要用到Linux自带的用于例行性工作调度的at和crontab两个命令了。其中at是仅执行一次的命令,这次先不谈,而crontab是循环执行的,符合定时清理文件的需求。cron这个系统服务是默认启动的,当用户用crontab这个命令新建工作调度后,该项工作就会被记录到/var/spool/cron/里面去了,而且是以账号作为判别,比如kindy使用crontab后,他的工作被记录到/var/spool/cron/kindy里面去。这个文件不能直接用vi编辑,而需要借助命令crontab.crontab [-u username] [-l|-e|-r]-u 只有root可以执行这个任务,不常用-e 编辑crontab工作内容,常用!-l 查阅crontab工作内容,常用!默认情况下,任何用户只要不被列入/etc/cron.deny中,那么他就可以执行crontab -e去编辑自己的定时任务了如果要定时清除的日志是已知的固定路径下的文件,比如//logs,我们规定每天23:59定时去清除最后修改时间在7天以前的文件,那么首先编辑任务:crontab -e进入后会看到每项工作的格式是怎么定义的,然后按需求直接写任务:23 59 * * * root find //logs -name ‘catalina.out*.log’ -and -mtime +7 -type f |xargs rm再退出,以后就会定时执行啦~

March 9, 2019 · 1 min · jiezi

开发函数计算的正确姿势——使用 brotli 压缩大文件

大文件问题函数计算对上传的 zip 代码包尺寸限制为 50M。某些场景中代码包中会超过这一限制,比如二进制 serverless-chrome 经过一番裁剪以后 ZIP 压缩包的体积为 43.4M,类似的还有 liboffice ,此外常见的还有机器学习训练的模型文件。目前解决大文件问题有三种方法采用更高压缩比的算法,比如本文介绍的 brotli 算法采用 OSS 运行时下载采用 NAS 文件共享简单的比较一下这三种方法的优劣方法优点缺点高密度压缩发布简单,启动最快上传代码包较慢;要写解压代码;大小受限制不超过 50 MOSS下载解压后文件不超过 512 M需要预先上传至 OSS;要写下载和解压代码,大概 50M/s 的下载速度NAS文件大小没有限制,无需压缩需要预先上传至 NAS;VPC 环境有冷启动时延(~5s)正常情况下如果代码包能控制在 50M 以下启动较快。而且工程上也比较简单,数据和代码放在一起,不需要额外的写脚本去同步更新 OSS 或者 NAS。压缩算法Brotli 是 Google 工程师开发的开源压缩算法,目前已经被新版的主流浏览器支持,作为 HTTP 传输的压缩算法。下面是在网上找到的关于 Brotli 和其他常见压缩算法对比基准测试。从上面三幅图我们可以看出:相比于 gzip、xz 和 bz2,brotli 有最高的压缩比,接近于 gzip 的解压速度,以及最慢的压缩速度。然而在我们的场景对于压缩慢这一缺点不敏感,压缩任务只要在开发准备物料的阶段执行一次就好了。制作压缩文件下面我先介绍一下如何制作压缩文件。下面的代码和用例都来自于项目 packed-selenium-java-example 。安装 brotli 命令Mac 用户brew install brotliWindows 用户可以去这个界面下载,https://github.com/google/brotli/releases打包并压缩打包前两个文件大小分别为 7.5M 和 97M╭─ ~/D/test1[◷ 18:15:21]╰─ lltotal 213840-rwxr-xr-x 1 vangie staff 7.5M 3 5 11:13 chromedriver-rwxr-xr-x 1 vangie staff 97M 1 25 2018 headless-chromium使用 GZip 打包并压缩,大小为 44 M。╭─ ~/D/test1[◷ 18:15:33]╰─ tar -czvf chromedriver.tar chromedriver headless-chromiuma chromedrivera headless-chromium╭─ ~/D/test1[◷ 18:16:41]╰─ lltotal 306216-rwxr-xr-x 1 vangie staff 7.5M 3 5 11:13 chromedriver-rw-r–r– 1 vangie staff 44M 3 6 18:16 chromedriver.tar-rwxr-xr-x 1 vangie staff 97M 1 25 2018 headless-chromiumtar 去掉 z 选项再打包一遍,大小为 104M╭─ ~/D/test1[◷ 18:16:42]╰─ tar -cvf chromedriver.tar chromedriver headless-chromiuma chromedrivera headless-chromium╭─ ~/D/test1[◷ 18:17:06]╰─ lltotal 443232-rwxr-xr-x 1 vangie staff 7.5M 3 5 11:13 chromedriver-rw-r–r– 1 vangie staff 104M 3 6 18:17 chromedriver.tar-rwxr-xr-x 1 vangie staff 97M 1 25 2018 headless-chromium压缩后的大小为 33M,相比 Gzip 的 44M 小了不少。耗时也非常的感人 6 分 18 秒,Gzip 只要 5 秒。╭─ ~/D/test1[◷ 18:17:08]╰─ time brotli -q 11 -j -f chromedriver.tarbrotli -q 11 -j -f chromedriver.tar 375.39s user 1.66s system 99% cpu 6:18.21 total╭─ ~/D/test1[◷ 18:24:23]╰─ lltotal 281552-rwxr-xr-x 1 vangie staff 7.5M 3 5 11:13 chromedriver-rw-r–r– 1 vangie staff 33M 3 6 18:17 chromedriver.tar.br-rwxr-xr-x 1 vangie staff 97M 1 25 2018 headless-chromium运行时解压缩下面以 java maven 项目为例添加解压依赖包<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.18</version></dependency><dependency> <groupId>org.brotli</groupId> <artifactId>dec</artifactId> <version>0.1.2</version></dependency>commons-compress 是 apache 提供的解压缩工具包,对于各种压缩算法提供一致的抽象接口,其中对于 brotli 算法只支持解压,这里足够了。org.brotli:dec 包是 Google 提供的 brotli 解压算法的底层实现。实现 initialize 方法public class ChromeDemo implements FunctionInitializer { public void initialize(Context context) throws IOException { Instant start = Instant.now(); try (TarArchiveInputStream in = new TarArchiveInputStream( new BrotliCompressorInputStream( new BufferedInputStream( new FileInputStream(“chromedriver.tar.br”))))) { TarArchiveEntry entry; while ((entry = in.getNextTarEntry()) != null) { if (entry.isDirectory()) { continue; } File file = new File("/tmp/bin", entry.getName()); File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } System.out.println(“extract file to " + file.getAbsolutePath()); try (FileOutputStream out = new FileOutputStream(file)) { IOUtils.copy(in, out); } Files.setPosixFilePermissions(file.getCanonicalFile().toPath(), getPosixFilePermission(entry.getMode())); } } Instant finish = Instant.now(); long timeElapsed = Duration.between(start, finish).toMillis(); System.out.println(“Extract binary elapsed: " + timeElapsed + “ms”); }}实现 FunctionInitializer 接口的 initialize 方法。解压过程刚开始是四层嵌套流,作用分别如下:FileInputStream 读取文件BufferedInputStream 提供缓存,介绍系统调用带来的上下文切换,提示读取的速度BrotliCompressorInputStream 对字节流进行解码TarArchiveInputStream 把 tar 包里的文件逐个解出来然后 Files.setPosixFilePermissions 的作用是还原 tar 包中文件的权限。代码太长此处略去,参阅 packed-selenium-java-exampleInstant start = Instant.now();…Instant finish = Instant.now();long timeElapsed = Duration.between(start, finish).toMillis();System.out.println(“Extract binary elapsed: " + timeElapsed + “ms”);上面的代码段会打印出解压的耗时,真实执行大概在 3.7 s 左右。最后不要忘记在 template.yml 里配置上 Initializer 和 InitializationTimeout参考阅读https://www.opencpu.org/posts/brotli-benchmarks/https://github.com/vangie/packed-selenium-java-example本文作者:倚贤阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 2 min · jiezi

【docker】windows 10 以下的电脑 解决docker终端启动失败的问题

在win10 64位机器上安装docker比较简单,直接安装官方的安装包即可开始使用docker,在win10以下的机子上安装docker稍微复杂一点win10以下的电脑安装dockerhttps://docs.docker.com/toolbox/toolbox_install_windows/1.1 下载安装上面的toolbox包。期间弹出的一切驱动安装提示均点击同意。安装好后桌面会多出几个东西。点击其中的Docker Quickstart Terminal,会自动进行配置。如果你双击Docker Quickstart Terminal提示找不到bash,那么你需要自己去定位bash的位置。右键更改Docker Quickstart Terminal的属性,将目标那里替换成你的git bash的位置。1.2 配置完毕,若终端出现鲸鱼则docker可以正常使用了。1.3 如果仅显示docker running,或者终端卡住了,则还需手动进行配置。我就被卡住了 ^_^。随便打开一个终端,在终端中输入docker-machine env default如果显示: Error checking TLS connection: Host is not running,那么先在终端中输入:docker-machine start default,之后再输入docker-machine env default如果你用的windows的cmd终端,应该会看到如下:将出现在你的终端上的上图红框部分的命令复制到终端上执行,然后一切搞定!你就可以开始使用docker了!注意每个终端的命令是不一样的。

March 8, 2019 · 1 min · jiezi

认识 Here Document

基础HereDoc 全名叫做 Here Document,中文可以称之为 嵌入文档。对它的叫法实际上很多,here文档,hereis,here-string 等等都是它。嵌入文档是 Shell I/O 重定向功能的一种替代。我们已经知道 Shell 的 I/O 重定向是一种文件句柄的传输。例如:COMMAND 1>/tmp/1.lst 2>&1将命令的标准输出为一个文件,同时也将错误输出到同一个文件中。而下例:cat /etc/passwd | grep ‘^admin:‘则通过管道将前一命令的输出当做后一命令的标准输入。基本语法Here Document 就是标准输入的一种替代品。它使得脚本开发人员可以不必使用临时文件来构建输入信息,而是直接就地生产出一个文件并用作命令的标准输入。一般来说其格式是这样的:COMMAND <<IDENT…IDENT在这里,<< 是引导标记,IDENT 是一个限定符,由开发人员自行选定,两个 IDENT 限定符之间的内容将被当做是一个文件并用作 COMMAND 的标准输入。例如echo大段文本时,我们可以使用 cat file 的语法:cat <<EOFSOME TEXTHERE!EOF此例中,我们使用 EOF 短语作为限定符。Here Document 是可以嵌套的,只要双层分别使用不同的 IDENT 限定符且保证正确的嵌套关系即可:ssh user@host <<EOTls -la –colorcat <<EOFfrom a remote hostEOF[ -f /tmp/1.tmp ] && rm -f /tmp/1.tmpEOT看起来有点怪?其实还好啦。实际上,限定符可以取得非常长,只要是字母开头且只包含字母和数字(通常,下划线和短横线也是有效的,不过根据 bash 的版本不同、宿主实现的不同,可能会有一定的出入)即可。abs 中有一个例子,节选如下:wall <<zzz23EndOfMessagezzz23fdjsldjfdsjlfdsjfdlszzz23EndOfMessagezzz23这是正确有效的,不过这个其实更怪一些。Here String在 bash, ksh 和 zsh 中,还可以使用 Here String:$ tr a-z A-Z <<<“Yes it is a string"YES IT IS A STRING此时也可以使用变量:$ tr a-z A-Z <<<"$var"不常见的用法同时重定向标准输出有没有可能将HEREDOC存储为一个文件?显然是可以:cat << EOF > /tmp/yourfilehereThese contents will be written to the file. This line is indented.EOF你可以注意到这种写法不同于经常性的写法:cat >/tmp/1<<EOFsEOF但两者都是对的。root但当需要 root 权限时,’>’ 并不能很好地工作,此时需要 sudo tee 上场:cat <<EOF | sudo tee /opt/1.logsEOF子shell标准输出的重定向,还可以通过子 shell 的方式来构造:(echo ‘# BEGIN OF FILE | FROM’ cat <<- EOF LogFile /var/log/clamd.log LogTime yes DatabaseDirectory /var/lib/clamav LocalSocket /tmp/clamd.socket TCPAddr 127.0.0.1 SelfCheck 1020 ScanPDF yes EOF echo ‘# END OF FILE’) > /etc/clamd.conf这个例子只是一个示意,因为实际上该例子用不着那么麻烦,单个 cat HEREDOC 足够达到目的了,也不需要开子 shell 那么重。cat <<EOF 的少见的变形let() { res=$(cat)}let <<‘EOF’…EOF元芳,你怎么看?还可以写作这样:let() { eval “$1”’=$(cat)’}let res<<‘EOF’…EOF当然,其实它和单行指令是等效的:{ res=$(cat); } <<‘EOF’…EOF{} 是语句块,而不是子shell,因而更省力。根据具体情况来使用它,有时候你希望子 shell 的变量无污染的效果,或者别的期待,那你就使用 ()。在参数展开语法中使用 HEREDOCvariable=$(cat <<SETVARThis variableruns over multiple lines.SETVAR)echo “$variable"示例展示了在 $() 语法中可以随意地嵌入 HEREDOC。如果你只是需要为变量用 HEREDOC 赋值,read var 通常是更好的主意:read i <<!Hi!echo $i # Hi对函数使用 HEREDOCGetPersonalData () { read firstname read lastname read address read city read state read zipcode} # This certainly appears to be an interactive function, but . . .# Supply input to the above function.GetPersonalData <<RECORD001BozoBozeman2726 Nondescript Dr.BozemanMT21226RECORD001echoecho “$firstname $lastname"echo “$address"echo “$city, $state $zipcode"echo可以看到,只要函数能够接收标准输入,那就可以将 HEREDOC 套用上去。匿名的 HEREDOC#!/bin/bash# filename: aa.sh: <<TESTVARIABLES${UX?}, ${HOSTNAME?} | ${USER?} | ${MAIL?} # Print error message if one of the variables not set.TESTVARIABLESexit $?这个示例中,如果变量没有被设置,则会产生一条错误消息,而该 HEREDOC 的用处实际上是用来展开要确认的变量,HEREDOC产生的结果作为 : 的标准输入,实际上被忽略了,最后只有 HEREDOC 展开的状态码被返回,用以确认是不是有某个变量尚未被设置:$ ./aa; echo $?./aa: line 3: UX: parameter null or not set1由于 UX 变量缺失,因此调用的结果是一行错误输出,以及调用的退出码为 1,也就是 false 的意思。: 是 true 命令的同义词。就好像 . 是 source 命令的同义词一样。进一步除了用来一次性检测一大批变量有否被赋值的效果之外,匿名的 HEREDOC 也常常被用作大段的注释。cat >/dev/null<<COMMENT…COMMENT: <<COMMENT…COMMENT这些写法都可以,看你的个人喜好。Bash 程序员的一般风格是能省键盘就省键盘。但有时候他们也喜欢能炫就炫::<<-! ____ _ ____ _ / | ___ ___ __| | / | ___ ___ __| || | _ / _ \ / _ \ / | | | _ / _ \ / _ \ / _ || || | () | () | (| | | || | () | () | (| | _|_/ _/ _,| _|_/ __/ _,| ____ _ _/ __|| | _ _ | | _ | __| | | |/ _| | | | ___) | |_| |_| | (_| | |_| ||____/ \__|\__,_|\__,_|\__, | |___/!while read当我们需要读一个csv文件时,我们会用到 while read 结构。将 csv 文件改为 HEREDOC:while read pass port user ip files directs; do sshpass -p$pass scp -o 'StrictHostKeyChecking no' -P $port $files $user@$ip:$directsdone &lt;&lt;____HERE PASS PORT USER IP FILES DIRECTS . . . . . . . . . . . . . . . . . . PASS PORT USER IP FILES DIRECTS____HERE由于不同格式的 CSV 的处理并非本文的主题,因此这里不再展开讨论具体情况了。补充:循环的重定向对于 while … done 来说,标准输入的重定向应该写在 done 之后。同样的,for … do … done 也是如此,until … done 也是如此。whilewhile [ "$name" != Smith ] # Why is variable $name in quotes?do read name # Reads from $Filename, rather than stdin. echo $name let "count += 1"done &lt;"$Filename" # Redirects stdin to file $Filename. untiluntil [ "$name" = Smith ] # Change != to =.do read name # Reads from $Filename, rather than stdin. echo $namedone &lt;"$Filename" # Redirects stdin to file $Filename. forfor name inseq $line_count` # Recall that “seq” prints sequence of numbers.# while [ “$name” != Smith ] – more complicated than a “while” loop –do read name # Reads from $Filename, rather than stdin. echo $name if [ “$name” = Smith ] # Need all this extra baggage here. then break fi done <"$Filename” # Redirects stdin to file $Filename. 新的缩进和对齐语法删除 TAB 缩进字符<<-IDENT 是新的语法,市面上的 Bash 均已支持这种写法。它的特殊之处就在于 HEREDOC 正文内容中的所有前缀 TAB 字符都会被删除。这种语法往往被用在脚本的 if 分支,case 分支或者其他的代码有缩进的场所,这样 HEREDOC 的结束标记不必非要在新的一行的开始之处不可。一方面视觉效果上 HEREDOC 跟随了所在代码块的缩进层次,可读性被提升,另一方面对于许多懒惰的编辑器来说,不会发生面对 HEREDOC 时语法分析出错、代码折叠的区块判断不正确的情况。function a () { if ((DEBUG)); then cat <<-EOF French American - Uses UTF-8 Helvetica - Uses RTL EOF fi}如上的脚本段落中,结束标记EOF可以不必处于行首第一个字母,只要EOF以及其上的HEREDOC正文都以TAB字符进行缩进就可以了。注意如果TAB字符缩进在这里没有被严格遵守的话,Bash解释器可能会报出错误。像在正文中的 - Uses UTF-8 除开行首的 TAB字符缩进之外,还包含两个空格字符,这不会受到 <<- 的影响而被删除。禁止变量展开一般情况下,HEREDOC 中的 ${VAR},$(pwd),$((1+1)) 等语句会被展开,当你想要编写 ssh 指令时,可能你希望的是不要展开 $ 标记。这可以用 <<“EOF” 来实现。只需要在 IDENT 标记上加上引号包围就可以达到效果,结束标记则无需引号。cat <<“EOF"Command is: $ lookup fantasyEOF# 如果不想展开,则你需要对 $ 字符进行转义cat <<EOF $ lookup fantasyEOF这个例子中,请注意单个的 $ 字符其实是不会展开也不会报错的,所以我们只是为了编写一个示例而已。引号包围呢,单引号、双引号都可以,都会同样地生效。甚至,你可以使用转义语法,也就是说:cat <<\EOFCommand is: $ lookup fantasyEOF也能禁止参数展开。同时应用上两者上面两个新的语法特性,是可以被同时组合和运用的: cat <<-“EOF” Command is: $ lookup fantasy EOF虽然你可能根本不需要遇到这样的情形。参考Advanced Bash-Scripting Guide - Chapter 19. Here Documents维基:Here文档 ...

March 6, 2019 · 4 min · jiezi

zsh 隐藏用户名和主机

修改 zshrc推荐修改方式vim ~/.zshrc# 重写 prompt_contextprompt_context () {}# 也可以使用自定义提示符prompt_context () { prompt_segment black default “hoo”;}修改主题不推荐直接修改主题,更新或者更换主题需要重新修改以 agnoster 为例,可以注释 prompt_context 或者也可以修改内容。注释中也有提示,不需要显示的可以隐藏,不会影响别的组件的展示vim ~/.oh-my-zsh/themes/agnoster.zsh-theme# Each component will draw itself, and hide itself if no information needs to be shown# Context: user@hostname (who am I and where am I)prompt_context() { if [[ “$USER” != “$DEFAULT_USER” || -n “$SSH_CLIENT” ]]; then prompt_segment black default “%(!.%{%F{yellow}%}.)%n@%m” fi}## Main promptbuild_prompt() { RETVAL=$? prompt_status prompt_virtualenv prompt_context prompt_dir prompt_git prompt_bzr prompt_hg prompt_end} ...

March 5, 2019 · 1 min · jiezi

git 入门

进入文件夹 cd xxxxx(要进入文件名)git init进入某个空的文件夹下,打开Git Bash命令窗口输入git init主要用来初始化一个空的git本地仓库。执行完上面的命令,当前目录下会自动生成.git隐藏文件夹,该隐藏文件夹就是git版本库touch 11.txt创建一个11.txt的文件git add .将工作区内容添加到暂存区在运行git commit命令之前,必须使用 git add 命令将任何新的或修改的文件添加到索引 git status -sb查看状态 绿色表示已提交git commit -v从暂存区提交到本地区 git commit -m可以加备注 也可以 git commit直接提交

February 16, 2019 · 1 min · jiezi

bash快捷键整理

转载请注明文章出处:https://tlanyan.me/bash-short…今天敲命令行时想在单词间移动,竟然不记得快速移动的快捷键。试了Ctrl + W和Ctrl + B/F均不凑效,于是怀念之前某博主分享的终端快捷键说明图。好在很快从浏览记录中找到了链接。原博主(@linuxtoy)分享的终端跳转解说图为:<img src=“https://tlanyan.me/wp-content...; alt=”" width=“858” height=“288” class=“aligncenter size-full wp-image-3247” />虽然命令难记,但解说图清晰易懂,让人印象深刻。趁热打铁,马上找了篇解说bash快捷键的文章温习一下。博主Alan Skorkin的"Bash Shortcuts For Maximum Productivity"一文写得十分详细,故在此翻译和分享,并部分内容做注释和修正。Alan Skorkin将bash快捷键分成四类,接下来一一给出。编辑Ctrl + a – 跳到行首Ctrl + e – 跳到行尾Ctrl + k – 删除当前光标至行尾内容Ctrl + u – 删除当前光标至行首内容Ctrl + w – 删除当前光标至词首内容Ctrl + y – 将剪切的内容粘贴在光标后Ctrl + xx – 在行首和当前光标处(来回)移动Alt + b – 跳到词首Alt + f – 跳到词尾Alt + d – 删除自光标处起的单词内容Alt + c – 大写光标处的字符(注:该条内容与原文不同)Alt + u – 大写自光标处起的单词内容Alt + l – 小写自光标处起的单词内容Alt + t – 将光标处单词与上一个词交换Ctrl + f – 向前移动一个字符(相当于按向左箭头)Ctrl + b – 向后移动一个字符(相当于按向右箭头)Ctrl + d – 删除光标后一个字符(相当于按Delete)Ctrl + h – 删除光标前一个字符(相当于按后退键)Ctrl + t – 交换光标处的两个字符搜索Ctrl + r – 反向搜索历史命令Ctrl + g – 退出历史搜索模式(相当于按Esc)Ctrl + p – 上一个历史命令(相当于按向上箭头)Ctrl + n – 下一个历史命令(相当于按向下箭头)Alt + . – 使用上一个命令的最后一个单词控制Ctrl + l – 清屏Ctrl + s – 终止输出到屏幕(对长时间运行并打印详细信息的命令)Ctrl + q – 允许输出到屏幕(如果之前用过终止输出命令)Ctrl + c – 终止命令Ctrl + z – 中断命令Bang(即感叹号)!! – 执行上一条命令!blah –执行最近运行过的以blah开头的命令!blah:p – 打印!blah要执行的命令(并将其作为最后一条命令加入到命令历史中)!$ – 上一条命令的最后一个单词 (等同于Alt + .)!$:p – 打印!$指代的单词!* – 上一条命令除最后一个词的部分!*:p – 打印!*指代部分如果错误,敬请指正!参考Bash Shortcuts For Maximum Productivity ...

February 11, 2019 · 1 min · jiezi

深究mv移动文件夹时`/`的用法

目标文件夹「不存在」的情况$ mv source target▲结果:把source文件夹「更名」为target目标文件夹「已存在」且为空$ mv source/ target$ mv source/ target/$ mv source target/$ mv source/ target/▲结果:将source移动到target下,成为子文件夹target source ……$ mv source/* target$ mv source/* target/▲结果:将source的所有内容移动到target下sourcetarget ……目标文件夹「已存在」且有冲突文件这就比较复杂了。如果有冲突文件,则会不询问直接覆盖。如果有冲突文件夹,则会把文件先全都移动过去,对于有冲突的文件夹,则无论如何都无法移动或覆盖。这时候要用cp -r命令先复制,在rm -r命令删除源文件夹。

January 26, 2019 · 1 min · jiezi

Bash脚本判别使用者的身份

经常要在bash脚本里面或者直接对脚本本身加上sudo运行命令,但是这引发了一系列的问题。比如用sudo的时候,脚本里的或$HOME指代用户文件夹的这个变量,到底是应该指向我真正的用户文件夹如/home/pi呢,还是指向了超级管理员的用户文件夹/root/呢?实际上它指向了/root/文件夹,这是我们绝对不想要的。但是很多命令如安装个程序,都不得不用sudo,那怎么办?首先要说下经验:命令行的权限执行,从表现上来看,可以分为以下5种情况:admin-manual: 普通用户手敲命令sudo-manual: 手敲命令加sudoadmin-bash: 以普通用户执行bash脚本sudo-bash: 以sudo执行bash脚本root-any: 以root用户登录很多变量、环境变量在这4中情况下,会经常出现混乱!(混乱指的是我们自己,不是电脑)另外,说个小技巧。我们都直到变量是指向当前用户目录,实际上abc格式的变量可以指向指定用户的用户目录,如pi会指向/home/pi,或ubuntu指向/home/ubuntu.理清一下思路:在正常执行脚本如./test.sh时是没有任何问题的,即使脚本里面出现了sudo如sudo apt-get update这样也是没有问题的。也就是说,就只有对整个脚本执行sudo的情况下如sudo ./test.sh,才会出现严重问题的!那么假设我的真实用户是pi,而HOME目录在/home/pi,现在我要在sudo ./test.sh这样的执行方式下找出正确的解决方案。以下为脚本中的各种语句和变量以及显示结果:# (不推荐!)$ whoami>>> root# 不同于whoami,能够指出当前有哪些用户登录电脑,包括本机登录和ssh登录的所有人$ who am i>>> 有些机器上显示为空>>> Mac上显示: pi ttys001 Nov 26 16:57# 等同于whoami (不推荐!)$ echo $USER>>> root# 用户主目录位置 (不靠谱不推荐!)echo $HOME>>> /root$ 用户主目录位置,等同于$HOME (不推荐!)$ echo >>> /root# 直接使用环境变量LOGNAME$ echo $LOGNAME>>> root# 显式调用环境变量LOGNAME $ printenv LOGNAME>>> root# SUDO_USER是root的ENV中的环境变量,# 同时普通用户的env是没有的,只有root用户才能显示出来$ sudo echo $SUDO_USER>>> pi# 显示调用环境变量SUDO_USER (不推荐!)# 从结果中可以看到,即使是sudo身份执行的脚本,脚本里面是否加sudo也会不同!$ printenv SUDO_USER>>> pi$ sudo printenv SUDO_USER>>> root从上面测试中可以看出,如果我们是用sudo执行bash脚本的话,很多变量都是“不靠谱”的。Stackoverflow中,比较一致性的倾向就是使用$SUDO_USER这个环境变量。而测试中也的确,它是最“稳定的”,即在不同的权限、OS系统下,都能始终如一(只限有sudo的系统)。那么现在我们有了用户名,就可以用pi这样的命令获取主目录/home/pi了,但是!这时候问题又出现了:手敲时候,我们可以获得pi的正确地址,但是脚本中却不识别pi是个什么东西,顶多是个字符串,没法像变量一样。那既然是这样,我们就不能用abc方法了,改用虽然老套但是绝对不混乱的方法:从/etc/passwd中直接看。手动的话可以直接打开passwd查看,脚本里面就比较麻烦,最方便的是用系统命令getent即Get Entries命令,获得指定用户的信息:$ getent passwd pi>>> pi❌1000:1000:,,,:/home/pi:/bin/bash那么,剩下的是有把其中的/home/pi取出来了,我们用cut就轻松取出。所以全部过程如下:me=$SUDO_USERmyhome=getent passwd $me | cut -d: -f 6顺利得到/home/pi!再进一步,如果脚本没有以sudo方式运行呢?这时候root用户和普通用户的环境变量下都是没有SUDO_USER这个变量的。那么就需要加一步判断了:me=${SUDO_USER:-$LOGNAME}myhome=getent passwd $me | cut -d: -f 6即如果SUDO_USER为空,则正常使用$LOGNAME获取当前用户。为什么不用$USER而是用$LOGNAME呢?因为USER不是每个系统都有,但是LOGNAME是*nix系统下都会有的。更新由于部分OS不能正确获取LOGNAME,所以统一采用uid的方式获取用户路径:HOUSE=getent passwd ${SUDO_UID:-$(id -u)} | cut -d: -f 6再更新MacOS没有/etc/passwd,也不支持getent passwd <UID>方式获取用户信息,但是sudo下也能保持$USER和$HOME变量内容不变。所以更改为下:HOUSE=${$(getent passwd ${SUDO_UID:-$(id -u)} | cut -d: -f 6):-$HOME}即如果getent方式无法获取内容,则直接取$HOME的值。再再更新因为bash不支持以上嵌套的三元运算表达式,所以要拆开:HOUSE="cat /etc/passwd |grep ${SUDO_UID:-$(id -u)} | cut -d: -f 6“HOUSE=${HOUSE:-$HOME}再再再更新如果是root的话,grep uid的时候会匹配到passwd中所有含0的行,所以要改进为以下:HOUSE="cat /etc/passwd |grep ^${SUDO_USER:-$(id -un)}: | cut -d: -f 6“HOUSE=${HOUSE:-$HOME} ...

January 26, 2019 · 1 min · jiezi

深究cp拷贝文件夹时`/`的用法

假设现有一个source文件夹:source sub a.jpg b.jpg c.jpg目标文件夹「不存在」的情况$ cp -r source target$ cp -r source/ target$ cp -r source/ target/▲ 结果:「source = target」以上三句话一样,都是创建一个source的同级克隆,只不过名字不同:target ……source ……$ cp -r source/* target▲ 结果:「命令错误」目标文件夹「已存在」且为空$ cp -r source target▲ 结果:「source ≠ target」无论是否有内容,都在target目录下存放source目录:source ……target source ……$ cp -r source/ target$ cp -r source/ target/$ cp -r source/* target$ cp -r source/* target/▲ 结果:「source = target」以上几句话一样,会正确的把source下的内容拷贝到target下target ……source ……目标文件夹「已存在」且不为空,且无同名文件$ cp -r source target$ cp -r source target/▲ 结果:「source ≠ target」无论是否有内容,都在target目录下存放source目录:source ……target source ……$ cp -r source/ target$ cp -r source/ target/$ cp -r source/* target$ cp -r source/* target/▲ 结果:「target ∋ source」会把source下的内容全部拷贝到targe之中source ……target …… ……目标文件夹「已存在」且有冲突文件▲结果:「默认覆盖有冲突的目标文件」无论怎么拷贝都默认覆盖 ...

January 26, 2019 · 1 min · jiezi

Centos7如何开启端口

CentOS7默认的防火墙不是iptables,而是firewalle.安装iptable iptable-service#先检查是否安装了iptablesservice iptables status#安装iptablesyum install -y iptables#升级iptablesyum update iptables #安装iptables-servicesyum install iptables-services 禁用/停止自带的firewalld服务#停止firewalld服务systemctl stop firewalld#禁用firewalld服务systemctl mask firewalld设置现有规则#查看iptables现有规则iptables -L -n#先允许所有,不然有可能会杯具iptables -P INPUT ACCEPT#清空所有默认规则iptables -F#清空所有自定义规则iptables -X#所有计数器归0iptables -Z#允许来自于lo接口的数据包(本地访问)iptables -A INPUT -i lo -j ACCEPT#开放22端口iptables -A INPUT -p tcp –dport 22 -j ACCEPT#开放21端口(FTP)iptables -A INPUT -p tcp –dport 21 -j ACCEPT#开放80端口(HTTP)iptables -A INPUT -p tcp –dport 80 -j ACCEPT#开放443端口(HTTPS)iptables -A INPUT -p tcp –dport 443 -j ACCEPT#允许pingiptables -A INPUT -p icmp –icmp-type 8 -j ACCEPT#允许接受本机请求之后的返回数据 RELATED,是为FTP设置的iptables -A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT#其他入站一律丢弃iptables -P INPUT DROP#所有出站一律绿灯iptables -P OUTPUT ACCEPT#所有转发一律丢弃iptables -P FORWARD DROP 其他规则设定#如果要添加内网ip信任(接受其所有TCP请求)iptables -A INPUT -p tcp -s 45.96.174.68 -j ACCEPT#过滤所有非以上规则的请求iptables -P INPUT DROP#要封停一个IP,使用下面这条命令:iptables -I INPUT -s ... -j DROP#要解封一个IP,使用下面这条命令:iptables -D INPUT -s ... -j DROP 保存规则设定#保存上述规则service iptables save开启iptables服务 #注册iptables服务#相当于以前的chkconfig iptables onsystemctl enable iptables.service#开启服务systemctl start iptables.service#查看状态systemctl status iptables.service 解决vsftpd在iptables开启后,无法使用被动模式的问题1.首先在/etc/sysconfig/iptables-config中修改或者添加以下内容添加以下内容,注意顺序不能调换IPTABLES_MODULES=“ip_conntrack_ftp"IPTABLES_MODULES=“ip_nat_ftp"2.重新设置iptables设置iptables -A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT 以下为完整设置脚本#!/bin/shiptables -P INPUT ACCEPTiptables -Fiptables -Xiptables -Ziptables -A INPUT -i lo -j ACCEPTiptables -A INPUT -p tcp –dport 22 -j ACCEPTiptables -A INPUT -p tcp –dport 21 -j ACCEPTiptables -A INPUT -p tcp –dport 80 -j ACCEPTiptables -A INPUT -p tcp –dport 443 -j ACCEPTiptables -A INPUT -p icmp –icmp-type 8 -j ACCEPTiptables -A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPTiptables -P INPUT DROPiptables -P OUTPUT ACCEPTiptables -P FORWARD DROPservice iptables save ...

January 22, 2019 · 1 min · jiezi

docker 容器不自动退出结束运行的方法

本文主要简单介绍 docker 容器与前置进程的关系,以及如何编写 Dockerfile/docker-compose.yml 优雅的让容器可以常驻运行。docker 容器的生命周期是同容器中的前置进程相关在一起的,这也是我们平时可能会遇到一些容器只是运行几秒便自动结束的原因:因为容器中没有一个常驻的前置进程,前置进程运行结束后,容器便自动退出了。比如 docker hello-world# 一闪而过 输出一堆东西docker run –name hello-world hello-world# 可以看到 hello-world 容器已经退出了docker ps -a那怎样可以让容器不自动退出呢?如果我们想登入一个纯净的容器 alpine/centos/ubuntu 之类的,在其基础上安装一些服务组件,然后在 commit 成自己的镜像。看网上有不少方法是创建容器时执行一个 while(true) 的死循环(当然,sleep 一下)或者用 tail -f /dev/null 一类的,反正就是以开启一个可以常驻的前置进程为目的。其实我们可以更优雅的使用 docker 容器的 interactive 和 tty 参数来将 sh/bash (*nix 系统必有)命令作为前置命令开启,这样容器就不会自动退出了。例如使用 alpine 镜像做为基础镜像,创建一个 alpine 系统小容器,让其可以常驻运行,以便我们登录交互执行某些命令。# 使用 alpine 系统镜像创建容器# -i interactive=true 开启 stdin# -t tty=true 分配会话终端# -d 守护模式 不加也可以 不加就直接进入容器中了 需要 ctrl+p+q 切出# 不能 exit 哟, exit 相当于结束 sh 会话了 容器会退出的docker run -it -d –name alpine alpine sh# alpine 肯定在运行docker ps# 登入容器docker exec -it alpine sh# apline 使用的 apk 作为包管理# 安装个小火车# 后续可以使用 docker commit -m “alpine with sl cmd” -a “big_cat” alpine big_cat/alpine_sl 生成新的镜像apk add sl# 退出容器 注:-d 启动的才可以,如果没有 -d 启动直接进入的 sh终端 则不能退出,否则容器也会退出exit提交容器变更生成新的镜像docker commit -m “alpine with sl cmd” -a “big_cat” alpine big_cat/alpine_sldocker images# 有账号的话发布到 docker hub 上去docker push big_cat/alpine_sl# 后续停止/启动容器时就不需要在指定 -it 参数了docker stop alpinedocker start alpine提交容器变更生成新的镜像docker commit -m “alpine with sl cmd” -a “big_cat” alpine big_cat/alpine_sldocker images# 有账号的话发布到 docker hub 上去docker push big_cat/alpine_sl以上命令其实是借助 sh/bash 会话终端作为前置进程,使得容器不会自动退出。如果你觉得在创建容器时如此书写会很粗陋,没关系,我们可以将这些都推给 docker-composedocker-compose.ymlversion: ‘3’services: big_cat_alpine: container_name: big_cat_alpine image: alpine stdin_open: true # -i interactive tty: true # -t tty privileged: true entrypoint: [“sh”] # 执行 sh创建容器 & 登入容器docker-compose up -d big_cat_alpine ./docker psdocker exec -it big_cat_alpine sh通过 docker-compose 将那两个参数传入进去,编排后启动服务容器。 ...

January 21, 2019 · 1 min · jiezi

[译] PEP 255--简单的生成器

我正打算写写 Python 的生成器,然而查资料时发现,引入生成器的 PEP 没人翻译过,因此就花了点时间翻译出来。如果在阅读时,你有读不懂的地方,不用怀疑,极有可能是我译得不到位。若出现这种情况,我建议你直接阅读原文,最好也能将错误处告知于我,以便做出修改。原文:https://www.python.org/dev/pe…创建日期:2001-05-18合入Python版本:2.2译者 :豌豆花下猫(Python猫 公众号作者)PEP背景知识 :学习Python,怎能不懂点PEP呢?摘要这个 PEP 想在 Python 中引入生成器的概念,以及一个新的表达式,即 yield 表达式。动机当一个生产者函数在处理某些艰难的任务时,它可能需要维持住生产完某个值时的状态,大多数编程语言都提供不了既舒服又高效的方案,除了往参数列表中添加回调函数,然后每生产一个值时就去调用一下。例如,标准库中的tokenize.py采用这种方法:调用者必须传一个 tokeneater 函数给 tokenize() ,当 tokenize() 找到下一个 token 时再调用。这使得 tokenize 能以自然的方式编码,但程序调用 tokenize 会变得极其复杂,因为它需要记住每次回调前最后出现的是哪个 token(s)。tabnanny.py中的 tokeneater 函数是处理得比较好的例子,它在全局变量中维护了一个状态机,用于记录已出现的 token 和预期会出现的 token 。这很难正确地工作,而且也挺难让人理解。不幸的是,它已经是最标准的解决方法了。有一个替代方案是一次性生成 Python 程序的全部解析,并存入超大列表中。这样 tokenize 客户端可以用自然的方式,即使用局部变量和局部控制流(例如循环和嵌套的 if 语句),来跟踪其状态。然而这并不实用:程序会变得臃肿,因此不能在实现整个解析所需的内存上放置先验限制;而有些 tokenize 客户端仅仅想要查看某个特定的东西是否曾出现(例如,future 声明,或者像 IDLE 做的那样,只是首个缩进的声明),因此解析整个程序就是严重地浪费时间。另一个替代方案是把 tokenize 变为一个迭代器【注释1】,每次调用它的 next() 方法时再传递下一个 token。这对调用者来说很便利,就像前一方案把结果存入大列表一样,同时没有内存与“想要早点退出怎么办”的缺点。然而,这个方案也把 tokenize 的负担转化成记住 next() 的调用状态,读者只要瞄一眼 tokenize.tokenize_loop() ,就会意识到这是一件多么可怕的苦差事。或者想象一下,用递归算法来生成普通树结构的节点:若把它投射成一个迭代器框架实现,就需要手动地移除递归状态并维护遍历的状态。第四种选择是在不同的线程中运行生产者和消费者。这允许两者以自然的方式维护其状态,所以都会很舒服。实际上,Python 源代码发行版中的 Demo/threads/Generator.py 就提供了一个可用的同步通信(synchronized-communication)类,来完成一般的任务。但是,这在没有线程的平台上无法运用,而且就算可用也会很慢(与不用线程可取得的成就相比)。最后一个选择是使用 Python 的变种 Stackless 【注释2-3】来实现,它支持轻量级的协程。它与前述的线程方案有相同的编程优势,效率还更高。然而,Stackless 在 Python 核心层存在争议,Jython 也可能不会实现相同的语义。这个 PEP 不是讨论这些问题的地方,但完全可以说生成器是 Stackless 相关功能的子集在当前 CPython 中的一种简单实现,而且可以说,其它 Python 实现起来也相对简单。以上分析完了已有的方案。其它一些高级语言也提供了不错的解决方案,特别是 Sather 的迭代器,它受到 CLU 的迭代器启发【注释4】;Icon 的生成器,一种新颖的语言,其中每个表达式都是生成器【注释5】。它们虽有差异,但基本的思路是一致的:提供一种函数,它可以返回中间结果(“下一个值”)给它的调用者,同时还保存了函数的局部状态,以便在停止的位置恢复(译注:resum,下文也译作激活)调用。一个非常简单的例子:def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b当 fib() 首次被调用时,它将 a 设为 0,将 b 设为 1,然后生成 b 给其调用者。调用者得到 1。当 fib 恢复时,从它的角度来看,yield 语句实际上跟 print 语句相同:fib 继续执行,且所有局部状态完好无损。然后,a 和 b 的值变为 1,并且 fib 再次循环到 yield,生成 1 给它的调用者。以此类推。 从 fib 的角度来看,它只是提供一系列结果,就像用了回调一样。但是从调用者的角度来看,fib 的调用就是一个可随时恢复的可迭代对象。跟线程一样,这允许两边以最自然的方式进行编码;但与线程方法不同,这可以在所有平台上高效完成。事实上,恢复生成器应该不比函数调用昂贵。同样的方法适用于许多生产者/消费者函数。例如,tokenize.py 可以生成下一个 token 而不是用它作为参数调用回调函数,而且 tokenize 客户端可以以自然的方式迭代 tokens:Python 生成器是一种迭代器,但是特别强大。设计规格:yield引入了一种新的表达式:yield_stmt:“yield”expression_listyield 是一个新的关键字,因此需要一个 future 声明【注释8】来进行引入:在早期版本中,若想使用生成器的模块,必须在接近头部处包含以下行(详见 PEP 236):from future import generators没有引入 future 模块就使用 yield 关键字,将会告警。 在后续的版本中,yield 将是一个语言关键字,不再需要 future 语句。yield 语句只能在函数内部使用。包含 yield 语句的函数被称为生成器函数。从各方面来看,生成器函数都只是个普通函数,但在它的代码对象的 co_flags 中设置了新的“CO_GENERATOR”标志。当调用生成器函数时,实际参数还是绑定到函数的局部变量空间,但不会执行代码。得到的是一个 generator-iterator 对象;这符合迭代器协议【注释6】,因此可用于 for 循环。注意,在上下文无歧义的情况下,非限定名称 “generator” 既可以指生成器函数,又可以指生成器-迭代器(generator-iterator)。每次调用 generator-iterator 的 next() 方法时,才会执行 generator-function 体中的代码,直至遇到 yield 或 return 语句(见下文),或者直接迭代到尽头。如果执行到 yield 语句,则函数的状态会被冻结,并将 expression_list 的值返回给 next() 的调用者。“冻结”是指挂起所有本地状态,包括局部变量、指令指针和内部堆栈:保存足够的信息,以便在下次调用 next() 时,函数可以继续执行,仿佛 yield 语句只是一次普通的外部调用。限制:yield 语句不能用于 try-finally 结构的 try 子句中。困难的是不能保证生成器会被再次激活(resum),因此无法保证 finally 语句块会被执行;这就太违背 finally 的用处了。限制:生成器在活跃状态时无法被再次激活:>>> def g():… i = me.next()… yield i>>> me = g()>>> me.next()Traceback (most recent call last): … File “<string>”, line 2, in gValueError: generator already executing设计规格:return生成器函数还可以包含以下形式的return语句:return注意,生成器主体中的 return 语句不允许使用 expression_list (然而当然,它们可以嵌套地使用在生成器里的非生成器函数中)。当执行到 return 语句时,程序会正常 return,继续执行恰当的 finally 子句(如果存在)。然后引发一个 StopIteration 异常,表明迭代器已经耗尽。如果程序没有显式 return 而执行到生成器的末尾,也会引发 StopIteration 异常。请注意,对于生成器函数和非生成器函数,return 意味着“我已经完成,并且没有任何有趣的东西可以返回”。注意,return 并不一定会引发 StopIteration :关键在于如何处理封闭的 try-except 结构。 例如:>>> def f1():… try:… return… except:… yield 1>>> print list(f1())[]因为,就像在任何函数中一样,return 只是退出,但是:>>> def f2():… try:… raise StopIteration… except:… yield 42>>> print list(f2())[42]因为 StopIteration 被一个简单的 except 捕获,就像任意异常一样。设计规格:生成器和异常传播如果一个未捕获的异常——包括但不限于 StopIteration——由生成器函数引发或传递,则异常会以通常的方式传递给调用者,若试图重新激活生成器函数的话,则会引发 StopIteration 。 换句话说,未捕获的异常终结了生成器的使用寿命。示例(不合语言习惯,仅作举例):>>> def f():… return 1/0>>> def g():… yield f() # the zero division exception propagates… yield 42 # and we’ll never get here>>> k = g()>>> k.next()Traceback (most recent call last): File “<stdin>”, line 1, in ? File “<stdin>”, line 2, in g File “<stdin>”, line 2, in fZeroDivisionError: integer division or modulo by zero>>> k.next() # and the generator cannot be resumedTraceback (most recent call last): File “<stdin>”, line 1, in ?StopIteration>>>设计规格:Try/Exception/Finally前面提过,yield 语句不能用于 try-finally 结构的 try 子句中。这带来的结果是生成器要非常谨慎地分配关键的资源。但是在其它地方,yield 语句并无限制,例如 finally 子句、except 子句、或者 try-except 结构的 try 子句:>>> def f():… try:… yield 1… try:… yield 2… 1/0… yield 3 # never get here… except ZeroDivisionError:… yield 4… yield 5… raise… except:… yield 6… yield 7 # the “raise” above stops this… except:… yield 8… yield 9… try:… x = 12… finally:… yield 10… yield 11>>> print list(f())[1, 2, 4, 5, 8, 9, 10, 11]>>>示例# 二叉树类class Tree: def init(self, label, left=None, right=None): self.label = label self.left = left self.right = right def repr(self, level=0, indent=" “): s = levelindent + self.label if self.left: s = s + “\n” + self.left.repr(level+1, indent) if self.right: s = s + “\n” + self.right.repr(level+1, indent) return s def iter(self): return inorder(self)# 从列表中创建 Treedef tree(list): n = len(list) if n == 0: return [] i = n / 2 return Tree(list[i], tree(list[:i]), tree(list[i+1:]))# 递归生成器,按顺序生成树标签def inorder(t): if t: for x in inorder(t.left): yield x yield t.label for x in inorder(t.right): yield x# 展示:创建一棵树t = tree(“ABCDEFGHIJKLMNOPQRSTUVWXYZ”)# 按顺序打印树的节点for x in t: print x,print# 非递归生成器def inorder(node): stack = [] while node: while node.left: stack.append(node) node = node.left yield node.label while not node.right: try: node = stack.pop() except IndexError: return yield node.label node = node.right# 练习非递归生成器for x in t: print x,printBoth output blocks display:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z问答为什么重用 def 而不用新的关键字?请参阅下面的 BDFL 声明部分。为什么用新的关键字yield而非内置函数?Python 中通过关键字能更好地表达控制流,即 yield 是一个控制结构。而且为了 Jython 的高效实现,编译器需要在编译时就确定潜在的挂起点,新的关键字会使这一点变得简单。CPython 的实现也大量利用它来检测哪些函数是生成器函数(尽管一个新的关键字替代 def 就能解决 CPython 的问题,但人们问“为什么要新的关键字”问题时,并不想要新的关键字)。为什么不是其它不带新关键字的特殊语法?例如,为何不用下面用法而用 yield 3:return 3 and continuereturn and continue 3return generating 3continue return 3return >> , 3from generator return 3return >> 3return << 3>> 3<< 3 3我没有错过一个“眼色”吧?在数百条消息中,我算了每种替代方案有三条建议,然后总结出上面这些。不需要用新的关键字会很好,但使用 yield 会更好——我个人认为,在一堆无意义的关键字或运算符序列中,yield 更具表现力。尽管如此,如果这引起足够的兴趣,支持者应该发起一个提案,交给 Guido 裁断。为什么允许用return,而不强制用StopIteration?“StopIteration”的机制是底层细节,就像 Python 2.1 中的“IndexError”的机制一样:实现时需要做一些预先定义好的东西,而 Python 为高级用户开放了这些机制。尽管不强制要求每个人都在这个层级工作。 “return”在任何一种函数中都意味着“我已经完成”,这很容易解读和使用。注意,return 并不总是等同于 try-except 结构中的 raise StopIteration(参见“设计规格:Return”部分)。那为什么不允许return一个表达式?也许有一天会允许。 在 Icon 中,return expr 意味着“我已经完成”和“但我还有最后一个有用的值可以返回,这就是它”。 在初始阶段,不强制使用return expr的情况下,使用 yield 仅仅传递值,这很简单明了。BDFL声明Issue引入另一个新的关键字(比如,gen 或 generator )来代替 def ,或以其它方式改变语法,以区分生成器函数和非生成器函数。Con实际上(你如何看待它们),生成器是函数,但它们具有可恢复性。使它们建立起来的机制是一个相对较小的技术问题,引入新的关键字无助于强调生成器是如何启动的机制(生成器生命中至关重要却很小的部分)。Pro实际上(你如何看待它们),生成器函数实际上是工厂函数,它们就像施了魔法一样地生产生成器-迭代器。 在这方面,它们与非生成器函数完全不同,更像是构造函数而不是函数,因此重用 def 无疑是令人困惑的。藏在内部的 yield 语句不足以警示它们的语义是如此不同。BDFLdef 留了下来。任何一方都没有任何争论是完全令人信服的,所以我咨询了我的语言设计师的直觉。它告诉我 PEP 中提出的语法是完全正确的——不是太热,也不是太冷。但是,就像希腊神话中的 Delphi(译注:特尔斐,希腊古都) 的甲骨文一样,它并没有告诉我原因,所以我没有对反对此 PEP 语法的论点进行反驳。 我能想出的最好的(除了已经同意做出的反驳)是“FUD”(译注:缩写自 fear、uncertainty 和 doubt)。 如果这从第一天开始就是语言的一部分,我非常怀疑这早已让安德鲁·库奇林(Andrew Kuchling)的“Python Warts”页面成为可能。(译注:wart 是疣,一种难看的皮肤病。这是一个 wiki 页面,列举了对 Python 吹毛求疵的建议)。参考实现当前的实现(译注:2001年),处于初步状态(没有文档,但经过充分测试,可靠),是Python 的 CVS 开发树【注释9】的一部分。 使用它需要您从源代码中构建 Python。这是衍生自 Neil Schemenauer【注释7】的早期补丁。脚注和参考文献[1] PEP-234, Iterators, Yee, Van Rossumhttp://www.python.org/dev/pep…[2] http://www.stackless.com/[3] PEP-219, Stackless Python, McMillanhttp://www.python.org/dev/pep…[4] “Iteration Abstraction in Sather” Murer, Omohundro, Stoutamire and Szyperski http://www.icsi.berkeley.edu/...[5] http://www.cs.arizona.edu/icon/[6] The concept of iterators is described in PEP 234. See [1] above.[7] http://python.ca/nas/python/g...[8] PEP 236, Back to the future, Petershttp://www.python.org/dev/pep…[9] To experiment with this implementation, check out Python from CVS according to the instructions at http://sf.net/cvs/?group_id=5470 ,Note that the std test Lib/test/test_generators.py contains many examples, including all those in this PEP.版权信息本文档已经放置在公共领域。源文档:https://github.com/python/peps/blob/master/pep-0255.txt(译文完)PS:官方 PEP 有将近500个,然而保守估计,被翻译成中文的不足20个(去重的情况下)。我好奇,感兴趣将一些重要的 PEP 翻译出来的人有多少呢?现抛此问题出来探探路,欢迎留言交流。—————–本文翻译并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

January 19, 2019 · 4 min · jiezi

树莓派基础-模拟信号和数字信号的区别

树莓派是很多程序员都喜欢玩的,我个人也很喜欢玩,开源接触到很多关于linux的知识,也可以通过读取硬件的数据来学习关于数据库的知识。前言本文详解阐释了模拟信号和数字信号的区别,希望帮助到有需要的朋友。数字信号使用树莓派上的GPIO引脚,很容易向输出组件发送信号并打开或关闭。还可以检测输入组件是否打开或关闭。以这种方式工作的组件称为数字组件(Digital components)。数字输出LED是数字输出元件的一个例子。它可以是打开的,也可以是关闭的,两者之间没有任何价值。我们可以把ON和OFF状态看作是1或0。你可以发送一个1给LED LED就会亮了,当你给LED发送0给 LED就熄灭了。图片描述数字输入一个按钮是数字输入组件。它可以是打开的,也可以是关闭的。当按钮被按下时,它向连接的树莓派GPIO引脚发送1。当按钮被释放时,它向GPIO引脚发送0。没有其他可以发送的值,因为你把按钮按到一半,幻想可以输出0.5。图片描述请看下面这个图,这个图显示了按钮被按压和释放的输入数据,当按压的时候输出1当释放的时候输出0。图片描述模拟信号数字输入和输出组件在Raspberry Pi中很常见,因为GPIO引脚都是数字的。它们只能发送或接收1和0。然而,并非所有组件都是数字的。有些被称为模拟组件。模拟部件可以在1和0之间发送和接收值。模拟输出电机是模拟输出元件的一个例子。你可以把它的值在1和0之间,这将控制电机的速度。如果你发送电机A 1,它将全速驱动。如果你把它发送0.5,它将以半速行驶。发送0将停止电机。图片描述模拟输入模拟输入元件的一个例子是光相关电阻器(LDR)。当没有光照在部件上时,它将发送0,并且随着光的增加,光敏发送的值将逐渐增加,直到它达到最大值1。图片描述下图显示了从LDR发送的信号在一天当中24小时的变化,随着光线越亮值越大,光线越暗值越小。图片描述使用树莓派的模拟组件比使用数字组件更加困难。为了使用GPIO引脚的模拟输出组件,您需要使用一种称为脉冲宽度调制(PWM)的技术。这向分量发送1秒和0秒的非常快的脉冲,当将其作为平均值时,可以将其接收为介于1和0之间的值。请看下面的图表。蓝线显示数字信号,在一段时间内,从0移动到1,然后再次返回。信号为1的是总时间的三分之一,剩下的三分之二是0。然后这个平均值约为0.33,这将是模拟组件接收到的值。你可以看到这是图上的红线。图片描述要使用模拟输入组件与GPIO引脚,您需要使用模拟到数字转换器(ADC),将模拟信号转换为数字信号。你可以买小的adc在你的电路使用。另外一种选择是在电路中使用电容和模拟元件。总结在树莓派上使用模拟输出是采用pwm技术,如果想要读取模拟输入那就需要ADC模拟到数字转换器,因为树莓派上没有ADC,因此我认为如果我们想要读取类似LDR或者模拟输入的情况,我们可以使用类似Nodemcu这种自带ADC的板子,然后再通过MQTT协议发送给树莓派,这样就很方便了。

January 18, 2019 · 1 min · jiezi

Fish Shell 使用笔记

安装Fish Shellbrew install fish安装Oh My Fishcurl -L https://get.oh-my.fish | fish安装Fishercurl https://git.io/fisher –create-dirs -sLo /.config/fish/functions/fisher.fish配置 autojumpclone autojumpgit clone https://github.com/wting/autojump.git安装 autojump 至本地 /.autojump 目录:cd autojump./install.py在fish配置中打开文件/.config/fish/config。在编辑器中查找并添加以下行:begin set –local AUTOJUMP_PATH $HOME/.autojump/share/autojump/autojump.fish if test -e $AUTOJUMP_PATH source $AUTOJUMP_PATH endend退出fish,重新开始。使用cd命令访问常用目录。你现在可以使用j命令跳转到这些目录:exitj testDir跳转到当前目录的子目录:jc chid_dir查看autojump历史记录中的条目统计信息:j -s使用finder打开目录jo dir配置nvm使用fish之后,之前配置的nvm就不能用了,需要在/.config/fish/config当中添加nvm的配 置begin set –local AUTOJUMP_PATH $HOME/.autojump/share/autojump/autojump.fish if test -e $AUTOJUMP_PATH source $AUTOJUMP_PATH end function nvm bass source ~/.nvm/nvm.sh –no-use ‘;’ nvm $argv endend默认shell切换至fishecho /usr/local/bin/fish | sudo tee -a /etc/shellschsh -s /usr/local/bin/fishswitch to fish Fish shell 入门教程 使用版本:Autojump 22.5.1, Fish 3.0.0和Mac 10.14.2 ...

January 10, 2019 · 1 min · jiezi

直击六大会场 | 洞察100+创新实践,2018TOP100summit圆满落幕!

北京时间11月30日-12月3日,由msup和中国国际人才交流基金会联合主办的第七届全球软件案例研究峰会(简称:TOP100summit)在北京国家会议中心圆满落幕。TOP100summit是科技界一年一度的案例研究峰会,每年甄选有学习价值的100件技术创新实践,分享他们在本年度最值得的总结、盘点的实践启示。今年,本届大会以“释放AI生产力,让组织向智能化演进”为主方向,来自全球范围内的100+年度优秀软件研发实践案例对2018年的行业发展进行了一次整体复盘。此外,数百位行业翘楚也给技术人带来了新一年的规划和参照。开幕式释放AI生产力,让组织向智能化演进_在11月30日举办的开幕式上,中国国际人才交流基金会主任苏光明先生,msup创始人兼CEO刘付强先生,京东副总裁、大数据平台负责人翁志先生,华为云首席产品专家汪维敏先生 ,搜狗语音交互技术中心语音首席科学家陈伟先生,yelp产品总监杨光先生,网易有道首席科学家段亦涛先生,快手产品副总裁徐欣先生,腾讯区块链高级产品总监秦青先生,分别从产品演进、团队管理、大数据、AI应用 、区块链技术等方面,对2018年软件研发现状及未来发展趋势发表了各自的观点,给诸多参会者带来了深入的思考。具体开幕式详情可点击:释放AI生产力,让组织向智能化演进 | 2018TOP100summit开幕式成功举办!本届大会分为产品、团队、架构、测试、运维及人工智能6个专场,在2018年软件研发行业的大背景下,人工智能、区块链、大前端等话题成为大会的新亮点。TOP100组委会历时125天,3000小时,180000分钟,邀请到了20+国外的讲师,百余位国内讲师共同为1500位参会者呈现出4天的技术盛会。产品创新/体验设计/运营增长_本专场由三位联席主席策划出品:快手产品副总裁徐欣、新浪微博用户运营总经理陈福云、阿里巴巴高级体验设计专家朱斌。纵观壹佰案例产品场榜单 ,不难发现今年甄选的案例全部围绕着互联网产品各个运营细节进行的讨论,也正是为了顺应当下产品的发展趋势,我们邀请了全球产品及运营大咖。本次峰会上,小米物流系统产品经理李宽、网易云信&七鱼市场总监卡爷、个推大数据产品咨询总监沈都分别为我们讲述了B端产品和C端产品根本性的区别、作为一个B端产品经理应该具备的技能以及具体的案例经验分享。卡爷在演讲中提到,数据需要不断测试持续优化 、要重视所有用户的营销价值、以客户为中心。随着AI的发展,人工智能技术已经逐渐在大部分企业落地,如何将人工智能技术结合到业务,形成真正的人工智能产品成为了他们新的诉求,我们邀请了Google产品经理何周舟从智能化产品管理的新挑战出发,带大家理解智能产品管理和直觉型以及经典型产品管理的差别,了解并填补相关鸿沟;“数字化转型”是近几年被高频提到的词汇,喜茶CTO陈霈霖作为众多案例中,唯一的零售餐饮企业为参会者分享了喜茶数字化转型过程中的核心逻辑——数字化三支柱;在无界零售时代的大潮中,京东无人超市产品负责人高颖运用京东X事业部“无人黑科技”的技术实现无人结算,为零售行业降本增效。腾讯CDC 高级设计师 and 产品经理魏仁佳&欧龙为我们深度讲述了To G 项目业务体系的复杂度,结合数字广东在推进政务服务不同触点的迭代阶段,缔造了“互联网+政务”体系。在用户增长领域,主要涉及到用户增长体系的搭建,滴滴、网易、slack、宜人贷、中原集团也分享了各自团队在用户增长方面的实践。滴滴出行体验战略负责人冯伟伦提到,体验需要可衡量、可监控、成体系、能比较。不同的商业模式和阶段,需要体验管理体系不断的迭代和更新。除了产品创新和运营增长外,体验设计也是产品经理离不开的话题,阿里、Spotify、微软、喜马拉雅、有赞的讲师也分别讲述了产品背后的设计思考。诸位产品经理、产品负责人演讲的议题涵盖了产品方法、产品设计、团队管理等核心方法论和进阶理论,结合了各自领域内一线实操经验,涉及了AI、新零售、小程序、增长黑客、社交零售、小程序、企业服务等多个互联网热点领域及方向。组织发展/研发效能/团队管理_本专场由三位联席主席策划出品:阿里巴巴钉钉技术管理负责人,资深技术专家杨威、美团·大众点评技术学院院长刘江、百度工程效率部总监李涛。技术的发展是无休止的,同时技术也存在繁多的问题:技术挑战,技术多样性导致协同困难;人才挑战,缺少跨界领域转接;交付挑战,业务复杂交付难度;组织挑战,协作壁垒严重阻碍合作进度;能力挑战,创新型企业缺少资金和市场能力等。作为技术团队的管理者,如何正确认识和把握团队的战略发展?敏捷、OKR、转型及项目管理的实践方式仍然是最普遍且一直持续使用的方法。本届峰会,来自阿里、美团、百度、平安银行、Trend Micro等企业的讲师分别从企业转型及研发效能方面分享敏捷和OKR的实践细节和操作经验。经过了几年的刷新变革,微软的市值翻番,超过了互联网泡沫以来的高点。微软Windows中国工程团队首席研发总监邹欣为我们讲述了在微软探索复兴之路的过程中,微软文化的重塑、软件工程的创新在管理上的变化。千人规模的研发团队,不同产品部门间既有共同产品规划目标,如何实现研发团队项目化管理,数字化和透明化呈现研发过程?用友开发管理总监侯洪志给出了答案 ;小游戏的项目管理在时间紧的情况下,如何快速地确定目标、规划好版本,制定可落地执行的计划?多人并行开发,如何保证人力最优?腾讯高级项目经理徐州分享了他的实践经验;伴随招银网络科技的发展,组织规模迅速扩大,如何快速提升工程师幸福感?招银网络科技文化牵头人苏妮分享了“Best Brain”项目打造组织技术氛围。作为技术管理者,不止需要协同协作,创新及流程化管理也是企业发展和技术变革的基础,同样将新的技术及思路融合进来,才能取得更十足的进步。爆款架构/数据平台/工程实践_本专场由三位联席主席策划出品:新浪微博研发中心研发总监李庆丰、前趣店集团总架构师&技术总监徐章健、京东副总裁、大数据平台负责人翁志。在数据时代,不少企业学会以数据驱动决策。但是,谈及实践,不少企业又犯了难:如何从海量数据中选择对业务增长有价值的部分?如何清洗并分析数据以驱动决策?如何不让庞大的数据降低整体计算性能?杨波从Uber的数据平台、对Apache Spark的使用,应用挑战和改进三方面全面介绍了Spark在Uber内部的架构设计及大规模实践。数字化转型时代,新技术正在逐渐颠覆传统行业。传统企业要想扭转颓势,必须了解数字化转型的特点以快速适应迅速变化的市场,Liberty Mutual资深产品经理吴疆介绍了Liberty Mutual从技术选型、开发流程等多维度开展数字化转型,并为参会者带来了新的启示。WeiboMesh源自于微博内部对异构体系服务化的强烈需求,同时像大多服务一样,都有历史包袱,网红微博搜索架构师丁振凯在现场为我们解读了现有服务如何高效平滑完成跨语言服务方案,并结合业务实例分析了WeiboMesh的独到之处。随着电商业务快速发展,各个业务系统已经演变成分布式、微服务化平台,因此理清各业务系统的链路关系,快速准确定位系统链路的性能瓶颈变得愈发困难。京东、当当、阿里、咸鱼等电商企业从技术选型到生产环境落地,对持续进化过程中遇到的问题进行了讲述。人工智能/AI驱动/AI实践_本专场由三位联席主席策划出品:网易传媒技术副总经理刘彦东、爱奇艺资深科学家王涛、小米首席架构师、人工智能与云平台副总裁崔宝秋。目前人工智能技术已经进入4.0时代,面对数字时代的飞速发展,前沿技术已悄然改变了人类的生活方式、沟通方式和商业架构。说到人工智能,机器学习作为人工智能的核心技术,是各个行业实践人工智能的基础。360商业产品事业部高级技术经理潘尧振演讲的《360易投放-用机器学习让广告投放进入“自动驾驶”时代》,通过使用机器学习技术帮助中小广告主自动生成和优化创意、快速搭建推广计划,实现“一站式”投放。近年来人工智能不仅正在各个生活场景中加速落地,还可以处理分析一些复杂的数据信息,来自腾讯互娱、来也、Keep、LG、Uber等公司的讲师带来对当下人工智能的相关技术原理和应用成果,并且延伸到在不同场景下的应用即对未来人工智能发展趋势的展望。测试实践/测试工具链建设/大前端&移动端_本专场由两位联席主席策划出品:甲骨文研发总监许峰兵、阿里巴巴淘宝技术质量负责人青灵。在测试技术的领域中,关于Weex、模糊测试、MBT及移动端自动化测试仍然是最热门的话题。本次峰会,深度链接 - 客户端测试效率提升之蹊径、手自一体化的移动云测试平台建设方案、基于机器学习的模糊测试在大型系统产品中的应用、Weex生态质量保障方案等演讲主题为参会者带来了新的启示。大前端技术,自从诞生以来就受到人们的广泛关注,经过几年的发展,这类技术开始落地。但由于大前端的演进与火爆,前端工程师的责任也越来越大,未来对前端岗位的要求也越来越高。同时,业务发展变化迅速,页面变来变去,前端人员忙不过来,但却职业提升缓慢?爱奇艺高级经理段金辰给出了答案;海量的用户规模、跨端用户环境、全球业务部署,给前端监控带来不少新的挑战,任职于阿里巴巴的前端技术专家彭伟春,分享了分前端监控最前沿的思考,到前端监控系统的架构,再到具体的案例分析,体系化地讲述前端监控的点点滴滴。运维体系/AIOps&DevOps/区块链_本专场由三位联席主席策划出品:蘑菇街技术总监赵成、腾讯区块链业务总经理蔡弋戈、JFrog中国区首席架构师王青。运维,是2018年最火的一个话题,也是最有前途的一个话题。随着运维收到越来越多的重视,运维业务多样化和业务规模也在持续增长,并开启了运维自动化向智能化的转型之路。成本、效率和质量是现在通用的运维主要面临的三大问题,三七互娱、百度、虎牙、腾讯、JFrog等团队分别分享了各自在运维平台整体架构建设方面的心得与经验。任职于三七互娱的运维开发负责人童传江分享了他在解决任何计算机问题的过程中,如何把复杂的问题抽象为简单的模块。近两年,DevOps正在成为大家所熟知的实践方法和文化价值观,对DevOps的采用已是主流趋势。京东的研发支持团队负责人唐洪山通过成熟的研发理念和DevOps实践,助力了企业效能提升和业务发展。随着近年来区块链技术的兴起,许多的企业也开始跟随时代开始着手研发,但在面对如此复杂的区块链知识时,一些问题与挑战也开始出现。腾讯、360、华为、中国民生银行等公司的区块链技术专家带来了自己的心得,和运维体系/AIOps&DevOps/区块链专场的每一位参会者共同交流区块链技术在企业中的应用。讲师晚宴_为打造更加深入的参会体验,本届TOP100summit于12月1日晚举办了讲师晚宴活动。多位讲师与部分参会者欢聚一堂,共同就软件研发行业的热点展开思考与共鸣。茶歇&展区互动_开放的茶歇场地,为参会者和大会讲师提供了自由的交流空间,同时由msup、buzz等多家合作伙伴精装打造的特色展位组成的用户体验区域,也为参会嘉宾带来了别样的感受,让参会嘉宾在玩味前沿技术更获得愉悦放松。本届2018TOP100summit讲师阵容中,国际化比例显著提升,大会不仅邀请了国内第一阶梯大厂的领袖,还邀请了来自 Google、yelp、微软、Linkedln、Uber、亚马逊等国际巨头企业负责人。他山之石可以攻玉,这些来自全球各地的不同的产品视角和产品方法,为参会用户带来了不一样的视野和收获。同时,我们在大会设置了反馈表环节,发现今年的评分远远高于往年,整体获得了9.2分以上的好评,在此,TOP100组委会感谢各位联席主席、讲师以及业内同仁的积极参与!此外,还要特别鸣谢华为、个推、来也、亿美软通、JFrog、APICloud、环信、图灵、华章、博文视点、青云、网易云信、猎聘等赞助伙伴及IT168等合作媒体伙伴的大力支持!

December 29, 2018 · 1 min · jiezi

Shell入门

1.运行1.1Shell分类:Linux 的 Shell 种类众多,常见的有:Bourne Shell(/usr/bin/sh或/bin/sh)Bourne Again Shell(/bin/bash)C Shell(/usr/bin/csh)K Shell(/usr/bin/ksh)Shell for Root(/sbin/sh)……常用的bash,即Bourne Again Shell1.2运行shell的两种方式作为可执行程序 将上面的代码保存为 test.sh,并 cd 到相应目录:chmod +x ./test.sh #使脚本具有执行权限./test.sh #执行脚本注意,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。 需要在第一行指定解释器,指定解释器的方法:#!/bin/bash作为解释器参数 这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,如:/bin/sh test.sh /bin/php test.php这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。2.变量定义变量 直接定义,不加美元符号$ eg:your_name=“runoob.com"使用变量echo $your_name #加$e推荐给变量加花括号{ }for skill in Ada Coffe Action Java; do echo “I am good at ${skill}Script"done只读变量 在变量前加readonly eg:readonly your_name删除变量 unset命令(只读变量不可以被删除) eg:unset variable_name单引号和双引号的区别单引号字符串的限制:单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。双引号的优点:双引号里可以有变量双引号里可以出现转义字符3.数组定义数组 用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为:数组名=(值1 值2 … 值n)单独定义:array_name[0]=value0读取数组${数组名[下标]}valuen=${array_name[n]}使用@获取数组中所有元素echo ${array_name[@]}获取数组长度 与获取字符串长度相同取得数组元素的个数length=${#array_name[@]}或者length=${#array_name[*]}取得数组单个元素的长度lengthn=${#array_name[n]}读取数组元素${array_name[index]}eg: ${my_array[0]}4.传递参数我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……$0 为执行的文件名在为shell脚本传递的参数中如果包含空格,应该使用单引号或者双引号将该参数括起来,以便于脚本将这个参数作为整体来接收。在有参数时,可以使用对参数进行校验的方式处理以减少错误发生:if [ -n “$1” ]; then ##中括号 [] 与其中间的代码应该有空格隔开 echo “包含第一个参数"else echo “没有包含第一参数"fi注意:的是中括号 [] 与其中间的代码应该有空格隔开5.计算原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。expr 是一款表达式计算工具,使用它能完成表达式的求值操作。例如,两个数相加(注意使用的是反引号 而不是单引号 '):val=expr 2 + 2`echo “两数之和为 : $val"下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]。 ...

December 27, 2018 · 1 min · jiezi

linux的开/关机脚本执行顺序和自启动脚本实践

linuxLinux是一种开源电脑操作系统内核。它是一个用C语言写成,符合POSIX标准的类Unix操作系统。90年代初期Linux开始出现的时候,仅仅是以源代码形式出现,用户需要在其他操作系统下进行编译才能使用。后来出现了一些正式版本。目前最流行的几个正式版本有:SUSE、RedHat、Fedora、Debian、Ubuntu、CentOS、Gentoo,等等。用户可根据自己的经验和喜好选用合适的Linux发行版。原先Linus Torvalds将Linux置于一个禁止任何商业行为的条例之下,但之后改用GNU通用公共许可证第二版。该协议允许任何人对软件进行修改或发行,包括商业行为,只要其遵守该协议,所有基于Linux的软件也必须以该协议的形式发表,并提供源代码。对于开发人员而言,很多时候都会接触到LINUX系统,甚至要去维护和开发,所以对开机/关机过程需要一定的认识,特别是服务器大部分都是linux系统的时候,我们重启服务、切换服务等都需要保障服务的功能不能中断,这就更需要对linux的运行过程有深入的认识,本文只是记录一小部分过程。Linux操作系统运行级别Linux 使用的是基于运行级(run-levels) 概念的称为 SysVinit 的专用启动工具。 它在不同的系统上可能是完全不一样的, 所以不能认为一个脚本在某个 Linux 发行版上工作正常。SysVinit以运行级的模式来工作,一般有 7 (从 0 到 6)个运行级 (实际上可以有更多的运行级,但都是用于特殊情况而且一般使用不到。 参见 init(8)以获得更多信息),每个运行级对应于一套设定好的任务, 当启动一个运行级的时候, 计算机就需要执行相应的任务。 默认的运行级是 3,下面是对不同运行级的描述:0: 关闭计算机1: 单用户模式2: 无网络多用户模式3: 有网络多用户模式4: 保留作自定义,否则同运行级 3 5: 同运行级 4,一般用于图形界面(GUI)登录(如 X的 xdm 或 KDE的 kdm) 6: 重启动计算机可以使用runlevel查看,也可以在/etc/inittab文件中看到以下:# Default runlevel. The runlevels used by RHS are:# 0 - halt (Do NOT set initdefault to this)# 1 - Single user mode# 2 - Multiuser, without NFS (The same as 3, if you do not have networking)# 3 - Full multiuser mode# 4 - unused# 5 - X11# 6 - reboot (Do NOT set initdefault to this)# id:3:initdefault:# System initialization.si::sysinit:/etc/rc.d/rc.sysinit也就是说我们系统的运行级别目前是3,那么需要看的就是/etc/rc3.d下的内容,其实里面都是软连接,所有的软链指向/etc/init.d/中的脚本文件,命名规范如下:[K|S][number][service name] –> ../init.d/servicename S 表示 Start,开启服务 [number]表示的是该脚本的运行优先级,number越小,脚本的运行优先级就越高 [service name]表示的是服务的名称。 因此我们如果需要定义一个服务启动的优先级(顺序),则需指定脚本的number即可。开机过程加载内核打开电脑电源,开始读取 BIOS 并进行主机的自我测试;透过 BIOS 取得第一个可开机装置,读取主要开机区 (MBR) 取得开机管理程式; LILO启动之后,如果你选择了Linux作为准备引导的操作系统,第一个被加载的东西就是内核。请记住此时的计算机内存中还不存在任何操作系统,PC(因为它们天然的设计缺陷)也还没有办法存取机器上全部的内存。因此,内核就必须完整地加载到可用RAM的第一个兆字节之内。为了实现这个目的,内核是被压缩了的。这个文件的头部包含着必要的代码,先设置CPU进入安全模式(以此解除内存限制),再对内核的剩余部分进行解压缩。执行内核内核在内存中解压缩之后,就可以开始运行了。此时的内核只知道它本身内建的各种功能,也就是说被编译为模块的内核部分还不能使用。最基本的是,内核必须有足够的代码设置自己的虚拟内存子系统和根文件系统(通常就是ext2文件系统)。一旦内核启动运行,对硬件的检测就会决定需要对哪些设备驱动程序进行初始化。从这里开始,内核就能够挂装根文件系统(这个过程类似于Windows识别并存取C盘的过程)。内核挂装了根文件系统之后,将启动并运行一个叫做init的程序。init进程init 程式开始执行系统初始化,最先读取/etc/inittab文件中的配置,配置中一般是先执行下 /etc/rc.d/rc.sysinit,然后执行/etc/init.d/functions ,设置环境变量等,同时读取runlevel的配置级别,对于嵌入式开发而言,在执行rcN.d之前会有/etc/init.d/rcS 单用户模式启动脚本的执行。 依据 init 的设定的initdefault进行 daemon start (/etc/rc.d/rc[0-6].d/*),依次执行脚本,启动服务。关机时依次关闭服务。rc0.drc.6.d文件夹下分别对应的是操作系统0-6级运行的状态下需要执行的脚本。在这些文件夹下的文件,都是软链文件,指向指定位置的脚本,这些软链都是指向同一个文件夹/etc/init.d/的脚本文件,载入本机设定 (/etc/rc.d/rc.local)rc.local文件,/etc/profile文件。这个文件是系统启动时,任何用户登录时执行的文件。任何用户登录前,root用户也会执行一遍。/.bash_profile文件。这个文件每个用户都有。它在用户登录时自动执行,拥有用户的权限。它export的环境变量对该用户随后启动的进程都有效。自启动脚本实践如果用户需要开机自动以root权限执行一些脚本,那么最好的方法是编辑/etc/rc.d/rc.local文件。如果每一个用户登录时都应该执行的脚本,如设置一些环境变量。那么应该修改/etc/profile文件。如果某一个特定用户登录时应该执行特定的脚本,如设置该用户特定的环境变量,应该修改~/.bash_profile文件。============/etc/profile============此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置.===========/etc/bashrc===========为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取.===============/.bash_profile===============每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件.=========/.bashrc=========该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该文件被读取.==========/.profile==========在Debian中使用.profile文件代 替.bash_profile文件.profile(由Bourne Shell和Korn Shell使用)和.login(由C Shell使用)两个文件是.bash_profile的同义词,目的是为了兼容其它Shell。在Debian中使用.profile文件代 替.bash_profile文件。==============/.bash_logout==============当每次退出系统(退出bash shell)时,执行该文件. Linux如何实现开机启动程序详解 ...

December 23, 2018 · 1 min · jiezi

Bash 记

若让计算机理解我的意图,可以有两种方式,说和指。这与生活中我为了让他人能够理解我的意图所采用的方式相似。譬如,我想让朋友去超市帮我买瓶饮料,我可以使用祈使句,「帮我去超市买瓶可乐,如何?」我也可以把他领到超市门口,指一下超市,然后再把他领进超市,指一下饮料柜里的一瓶可乐,然后再指一下他口袋里的钱包。基于说的方式让计算机理解我的意图,就是向计算机输入一些命令,计算机则通过命令解释程序理解我的意图。基于指的方式让计算机理解我的意图,就是通过鼠标、触摸屏之类的交互设备,向计算机输入一些坐标信息,计算机则通过图形界面程序理解我的意图。在生活中,人们通常会喜欢通过说的方式让他人理解自己的意图,只有在难以言说的时候,才会考虑用指的办法。这是因为我们已经掌握了一门与他人沟通交流所用的语言,利用这种语言让他人理解我们的意图,是成本最低的方法。但是对于计算机而言,由于大多数人通常没有机会学习计算机可以理解的语言,因此对于他们而言,采取指的方式让计算机理解他们的意图则是成本最低的方法。因为这个缘故,大多数人喜欢以图形界面交互的方式使用计算机,因为他们觉得学习一门计算机语言,成本太高。倘若将一门计算机语言列入小学课程,或许孩子们长大后与计算机的沟通便不再像现代人这样过度依赖图形界面交互方式。图形界面与命令并不冲突。我们教孩子说话,一开始也是以指物的方式,让孩子获得了基本的语言交流能力,在这个基础上再教他们识文断字。图形界面程序为计算机的普及作出了很大贡献,但是在这个基础上,若想让计算机更准确、高效地理解我们的意图,那么便需要学习一门计算机语言了。计算机语言不像人类语言那样丰富。因此,不要指望我们冲着计算机喊几嗓子,或者在运行着命令解释器的终端(命令行窗口)里输入「帮我写毕业论文」这样的句子,计算机就能够充分理解我们的意图,转而毫无怨言地去工作。至少直至目前,我们的计算机尚不具备这样的功能。不过,我们可以通过付费的方式,对那些擅长计算机语言的人喊几嗓子,或者给他们发几条微信。倘若想亲历亲为,而且认为与计算机这样的机器进行交流像是在玩一种文字意义上的游戏,那么便可以从学习一种命令解释程序入手,学习这个程序所支持的语言以及一些常用的命令。Bash 语言若打算学习计算机语言,不妨从 Bash 语言入手,把它作为「母语」。Bash 是一个命令解释程序,用于执行从标准输入或文件中读取的命令。能被 Bash 理解的语言即 Bash 语言。像 Bash 这样的程序通常称为 Shell,作为计算机用户操作计算机时的基本交互界面,亦即计算机用户通过 Shell 使用操作系统内核所提供的种种功能。Bash 是众多 Shell 中的一种,但它却是流传最为广泛的 Shell。在 Windows 系统中,Windows 10 以前的版本可以通过安装 Cygwin 便可获得 Bash,而在 Windows 10 中,只需开启 WSL(Windows Subsystem for Linux)便有了 Bash。至于 Linux 和 Mac OS X 系统,Bash 则是它们的核心标配组件。Android 手机上,通过 F-Droid 安装 Termux 也能得到 Bash。因为 Bash 几乎无处不在,而且它能够帮助我们处理许多计算机里的日常任务,所以不妨把它作为计算机世界里的母语去学习。计算机语言虽然种类繁多,但是用这些语言写的程序,通常可以作为命令在 Bash 或其他某种 Shell 环境中运行,这种做法颇类似于我们在母语的基础上学会了一些专业里的「行话」。事实上,在熟悉了 Bash 语言以及一些常用的命令之后,经常可以发现,很多人用其他编程语言所写的那些程序实际上没必要写,因为这些程序很有可能只需要一条 Bash 命令便可以实现了。譬如,你在撰写文档的时候,有时可能会想使用直角引号「 和 」,而非 “ 和 ”,但是自己所用的中文输入法里可能并不是很方便输入 「 和 」,这时该怎么办呢?没有多少好办法,除非你对这个输入法足够了解,去修改它的码表,为键盘上的 " 键与 「 和 」 建立映射。对于 Bash 而言,如果你的文档能够表现为纯文本的格式,假设文件名为 foo.txt ,那么你大可以继续使用 “ 和 ”,只需在文档定稿后,使用 sed 命令将 “ 和 ” 替换为 「 和 」,即$ sed -i ’s/“/「/g; s/”/」/g’ foo.txt已经习惯了图形界面交互方式与计算机沟通又抗拒学习计算机语言的人可能会抬杠,「我在微软 Word 里也可以用『查找/替换』的方式来完成你这条命令所能完成的任务啊!」诚然如此,但是如果手上不止有一份文件,而是有一组文件 foo-1.txt、foo-2.txt、foo-3.txt……需要作引号替换处理呢?难道要用微软 Word 逐一打开这些文件,然后作「查找/替换」处理,再逐一保存么?倘若这样做,就相当于是计算机在使用人,而非人使用计算机。对于 sed 命令而言,将某项文本处理过程作用于多份文件与作用于一份文件并无太大区别。若对 foo-1.txt、foo-2.txt、foo-3.txt……进行引号替换,只需$ sed -i ’s/“/「/g; s/”/」/g’ foo-*.txt庄子说,「指穷于为薪。火传也,不知其尽也。」倘若将「引号替换」视为「火」,那么用微软 Word 逐一打开文件,进行引号替换处理,再将处理后的文件保存起来,这种做法就是「指穷于为薪」,即使你很敬业,很努力,但是这样去做一辈子,在 sed 看来也只是完成了它在一瞬间就能做到的事。庄子若活到现在,一定是一个善于用计算机语言编程的人。终端心里至少要有一团光,哪怕它很微弱,只是来自一根火柴。在这样的微光里,看到的是世界是一片寂静的混沌,至少也是全貌。我的桌子上总是有一盒火柴,作为对童年爱放野火的冬天的怀念。我取出一根火柴,让它的头部迅捷有力地擦过磷纸,小小的火焰喷射而出,引燃了它的身体。我要带着这团火焰,进入一个诡异的黑暗世界,它的名字叫终端。终端是 Shell 的界面,人通过终端输入命令。Shell 从终端接到命令,然后理解命令,执行命令。不要忘记,Bash 是诸多 Shell 中的一种。在这个世界里,持着光,我注意的第一个东西是「$」,它的右面是一个不断明灭的矩形块,顶部是「」。「$」称作命令提示符。闪烁的矩形块表示可以在此输入命令。「」是我当前所在的位置——硬盘里的某个目录,亦即当前目录或当前工作目录。实际上 ~ 不过是 Bash 对我的个人主目录给出的简写,它的全称是「/home/garfileo」。倘若我此时正处于/tmp 目录,那么$ 的顶部应当是「/tmp」。对于终端的初始印象,你与我或许不同。若想看到我所看到的,需要在 ~/.bashrc 文件里添加PS1="\e[0;32m\w\e[0m\n$ “\e[0;32m 和 \e[0m 皆为 Bash 世界里的颜色值,前者表示绿色,后者表示无色;当二者之间出现 \w 时,所产生的效果是设定 \w 为绿色,但是不影响 \w 以外的字符。\w 表示当前目录。\n 表示换行。这种咒语级的代码,足以令很多人望 Bash 而生畏或生厌了。不必担心,这样的咒语并不经常需要念。倘若我在 ~/.bashrc 文件里删除上述对 PS1 的设定,那么我对终端的初始印象应当是 $ 的顶部没有东西,左侧是「garfileo@zero ~」,右侧是那个不断明灭的矩形块。或许你会喜欢我对 PS1 所作出的上述设定,特别是在你企图在命令提示符的右侧输入很长的命令之时。echo初次使用 Bash,也许尚不知如何向 ~/.bashrc 文件添加PS1="\e[0;32m\w\e[0m\n$ “不过,现在运行 Bash 的操作系统大多提供了图形界面。可以图形界面式的文件管理器里找到 .bashrc 文件,然后使用一种图形界面式的文本编辑器打开这份文件,添加上述内容,然后再保存文件。这是我们刚开始使用计算机的时候就已经学会了的方法。但是,从现在开始可不必如此,因为类似的任务通过一些简单的命令便可完成。例如,要完成上述任务,只需在终端的命令提示符后输入echo ‘PS1="\e[0;32m\w\e[0m\n$ “’ >> ~/.bashrc然后回车。之后,Bash 便会对我们输入的这一行文本予以理解和执行。我们输入的这行文本便是命令。echo 命令可将一行文本显示于终端。例如,$ echo ‘PS1="\e[0;32m\w\e[0m\n$ “‘Bash 执行这条命令之后,终端紧接着便会显示PS1="\e[0;32m\w\e[0m\n$ “就像是对空旷寂静的山谷喊了一声「PS1="\e[0;32m\w\e[0m\n$ “」,然后山谷给出了回音,echo 命令得名于此。亦即,echo 只是将它所接受的文本不加改变地输出。echo 本身是无用的。但是,当它的后面出现了 >> ~/.bashrc 时,它的输出便会被 >> 强行导入 ~/.bashrc 文件,此时没用的 echo 便发挥了作用。天生我材必有用。>> 是输出重定向符,因为它可以将一个命令的输出导向到指定的文件。当 >> 在 echo 命令之后出现时,它是如何得知 echo 命令的输出的呢?对我们而言,echo 命令的输出是在终端里呈现出来的,难道 >> 也能像我们这样「看到」终端里的内容么?我们看到的,并非全部。我们所看到的 echo 的输出,实际上是 echo 在一份文件里写入的内容,这份文件的名字叫 stdout(标准输出)。终端从 stdout 读取内容并显示于屏幕。>> 也能从 stdout 读取内容并写入其他文件。这些是我们看不到的部分。输入/输出重定向在终端的命令提示符后输入echo ‘PS1="\e[0;32m\w\e[0m\n$ “’ >> ~/.bashrc然后回车。Bash 是如何得到这行文本并将其作为命令予以执行的呢?依然是通过一份文件。这份文件的名字叫 stdin(标准输入)。我们在终端里输入任何信息,本质上都是在向 stdin 写入内容。当我们输入一行文本回车时,Bash 便开始读取 stdin 里的内容,把它们理解为一条命令或一组命令的组合,然后予以执行。对于 >> 指向的文件(命令中位于 >> 右侧的文件),>> 是不改变其原有内容的前提下将 stdout 中的内容写入该文件,亦即向该文件尾部追加信息。若期望用 stdout 中的内容替换该文件的原有内容,可使用 >。例如(仅仅是个例子,不要真的去这样做):$ echo ‘PS1="\e[0;32m\w\e[0m\n$ “’ > /.bashrc执行这条命令之后,/.bashrc 文件的全部内容便会被替换为「\e[0;32m\w\e[0m\n$ “」。任何命令,只要它能够向 stdout 输出信息,其输出皆能通过 > 和 >> 写入到指定文件。倘若所指定的文件不存在,Bash 会自动创建。因此,不妨将 Bash 的输出重定向符视为对各种文本编辑器或字处理软件的「文件(File)」菜单里的「另存为(Save as)」功能的抽象。Bash 也提供了输入重定向符 <,可将其视为对各种文本编辑器或字处理软件的「文件(File)」菜单里的「打开(Open)」功能的抽象。任何命令,只要它以 stdin 中的信息作为输入,皆能通过 < 将指定文件中的信息作为输入,因为 < 可将指定文件中的信息写入 stdin。例如,Bash 将 stdin 中的信息视为命令予以执行,倘若将某条命令写入一份文件,然后通过 < 将该文件作为 Bash 的输入,所产生的效果是,Bash 会将这份文件中的内容视为命令并予以执行。以下命令模拟了这一过程:$ echo ’echo “Hello world!”’ > /tmp/foo.sh$ bash < /tmp/foo.shHello wrold!在 Bash 中,可以执行 bash 命令,这一点细想起来会有些奇怪。不过,人类不是也经常将「自我」作为一种事物去思考么?实际上,上述 bash 命令中的输入重定向符是不必要的。因为 bash 命令原本便支持直接读取指定文件中的内容并将其视为命令予以执行,即$ bash /tmp/foo.sh输入重定向符主要面向那些只支持从 stdin 获取输入信息的程序,例如,用于计算凸包的程序 qhull 便是这样的程序。倘若机器上已经安装了 qhull 软件包,可以使用 rbox 命令生成含有三维点集的数据文件,然后通过 < 将数据文件中的三维点集信息传递于 qhull 程序:$ rbox c > points.asc$ qhull s n < points.asc之后,qhull 便会输出三维点集的凸包信息。管道认真观察$ echo ’echo “Hello world!”’ > /tmp/foo.sh$ bash < /tmp/foo.sh和$ rbox c > points.asc$ qhull s n < points.asc这两组命令是否相似?在形式上,它们都是一条命令通过输出重定向将自己原本要写入 stdout 的输出信息被重定向到了一份文件,另一条命令则是通过输入重定向将自己原本要从 stdin 里获取的输入信息变成了从指定文件中获取。这个过程类似于,你将信息写到了纸条上,然后又将纸条扔给我看。但是在生活中,我们通常不会这样麻烦,你会用嘴巴发出信息,我则用耳朵接受信息。Bash 也有类似的机制,名曰管道。基于管道,上述两组命令可简化为$ echo ’echo “Hello world!”’ | bash$ rbox c | qhull s n只要一条命令能通过 stdout 输出信息,而另一条命令能通过 stdin 获取信息,那么这两条命令便可以借助管道连接起来使用。说话要比写字快,而且能够节省纸张,管道的意义与之类似,不仅提高了信息的传递速度,而且不消耗硬盘。cat, mv 和 sed通过 >> 能够实现向一份文本文件尾部追加信息。有没有办法向一份文本文件的首部追加信息呢?这个问题并不简单。在生活中,有许多事需要我们排队。来得晚了,应当主动站在队尾,这样做不会影响对于早来的人。但是,若来得晚了,反而强行站到队伍的前面去了,可能会被挨打。也许我们都喜欢插队,但是却很少有人喜欢插队的人。所谓文件,事实上不过是一组排好了队的字节。因此,通过 >> 向一份文本文件尾部追加信息容易,但是若将信息插入到文件的首部,原有的文件内容在硬盘里的相应位置必然要发生变化。假设文件 foo.tex 的内容为\starttext这是一份 ConTeXt 文稿。\stoptext若想将「\usemodule[zhfonts]」增加到这份文件的首部,可通过以下命令实现:$ echo ‘\usemodule[zhfonts]’ > foo-tmp.tex$ cat foo.tex >> foo-tmp.tex$ mv foo-tmp.tex foo.texcat 命令用于将多份文本文件的内容连接起来,并将结果写入 stdout。当 cat 只处理一份文本文件时,产生的效果是将这份文件的内容写入 stdout。由于 stdout 中的内容可通过 >> 导入到指定文件,因此上述的 cat 命令所起到的作用相当于把 foo.tex 的内容复制出来,并追加到 foo-tmp.tex 的尾部。mv 命令可将文件从其当前所在目录移动到另一个指定目录,倘若这个指定目录依然是当前目录,那么 mv 命令便起到了文件重命名的效果。上述 mv 命令将 foo-tmp.tex 重命名为 foo.tex。最终得到的 foo.tex,便等价于在其原有的内容首部插入了「\usemodule[zhfonts]」。不过,能够运行 Bash 的环境,大多也提供了擅长处理文本编辑任务的 sed 程序。与 Bash 相似,sed 也能执行它能够理解的一组命令,这组命令专事于文本的编辑。例如,如果将'1 i \\usemodule[zhfonts]‘传递于 sed,并指使 sed 将此命令作用于 foo.tex,即$ sed -i ‘1 i &gt; \usemodule[zhfonts]’ foo.texsed 便会这条命令理解为,在 foo.tex 的第 1 行插入「\usemodule[zhfonts]」。注意,上述的 > 符号并非输出重定向符,它是终端的二级命令提示符。在终端中输入多行文本构成的命令时,终端自动给出二级命令提示符。还记得 $ 吧,之前将其称为命令提示符,实际上它是一级命令提示符,这也正是为何用于设定它的格式时是通过 PS1 的原因。类似地,可以通过 PS2 来定制二级命令提示符的格式。Bash 如何知道我们要输入多行文本呢,亦即在我们输完 sed -i ‘i 1 \ 并摁了回车键之后,为何 Bash 不认为我们已经将命令输入完毕呢?这是因为在上述命令中,在第一行的末尾出现的 \,会被 Bash 理解为续行符。回车键产生的换行符位于续航符之后,会被 Bash 忽略。利用续行符,可将较长的命令分成多行输入,Bash 会将最后一行的换行符作为命令输入完毕的信号。例如,$ echo ’echo “Hello world!”’ &gt; | bash这条命令虽然不长,但是足以说明续行符的用法。不过,上述的 sed 命令的第一行虽然在末尾出现了续行符,但实际上 Bash 是没有机会得知该续行符的存在。因为 sed 程序所接受的命令文本是拘禁在一对单引号中的,这种形式的文本叫做单引号字串。单引号字串中的续行符和换行符,Bash 会不予理睬。因此,上述的 sed 命令的第一行末尾的 \,实际上并非面向 Bash 而存在,「i \」实际上是 sed 程序的一个指令,用于在指定的文本行之前插入一行或多行给定的文本。因为 \ 对于 sed 程序有着特殊含义,因此在通过 sed 命令在 foo.tex 的首部添加「\usemodule[zhfonts]」时,必需对文本中的 \ 进行转义,由于 sed 程序是以 \ 为转义符,因此在 sed 命令中,「\usemodule[zhfonts]」必须写成「\usemodule[zhfonts]」。至此,是否觉察到了 Bash 语言的混乱之处?引号命令的输入数据和输出数据只有两种,文本与存储文本的文件。Bash 如何对它们予以区分呢?Bash 规定,凡出现在一对双引号 " 或出现在一对单引号 ’ 之内的文字便是文本,否则便是文件。不过,我们向 Bash 提交的一切皆为文本。为了进行区分,应当将作为命令的输入或输出数据的文本称为字串,并且出现在一对 " 之内的字串可称为双引号字串,出现在 ’ 之内的字串则称为单引号字串。问为什么会有两种字串,不如问为什么人类发明的文字里要有两种引号。在与他人的对话中,彼此所说的每句话可以用双引号包围起来。例如:我:“最近我在读《齐物论》。”你:“它讲了什么?”我:“庄子建议我们应当‘为是不用而寓诸庸’。”单引号可以出现在双引号里,这意味着什么?意味着双引号更为宽松,而单引号则比双引号更为紧致。宽松的可以包含紧致的。对于 Bash 而言,双引号字串的宽松体现为,双引号字串中如果含有一些对于 Bash 有特殊含义的字符,Bash 会使用这些字符的含义代替这些字符。例如,$ echo “$PS1"倘若期望这条 echo 命令能够输出 $PS1,那么结果就会令人失望了。这条命令输出的并非 $PS1,而是一开始我们在 ~/.bashrc 文件中为 PS1 所设定的「\e[0;32m\w\e[0m\n$ 」。单引号比双引号更为紧致,它可以阻止 Bash 对 $PS1 有其自己的理解。因此$ echo ‘$PS1’的输出结果方为「$PS1」。前文中的示例$ echo ’echo “Hello world!”’ | bash其中,双引号字串是放在单引号字串之内的,这与上述我虚拟的关于《齐物论》的对话中所用的引号的用法有所不同,但是在 Bash 语言中,只能这样去写。倘若将上述命令写成$ echo “echo ‘Hello world!’” | bashBash 对字串中出现的 ! 会有特殊的理解,因为 ! 是 Bash 命令,想不到吧?字串中若出现引号,需要使用 \ 对引号进行转义。例如,$ echo “I say: "Hello world!"“Bash 会认为 echo 的输入数据是「I say: “$hello”」。倘若去掉转移符 \,即$ echo “I say: “$hello"“Bash 会认为 echo 的输入数据为I say: $hello空字串在双引号字串中,为避免 Bash 对某些我们期望保持本义的字符产生误解,通常需要用转义符 \ 让 Bash 放弃这样的尝试。变量PS1 是一个名字,「\e[0;32m\w\e[0m\n$ 」是它指代的对象,$PS1 是引用这个对象。Bash 允许我们以这样的方式对数据予以命名,而后以名字指代数据。例如$ hello=“Hello world!"$ echo $hello与$ echo “Hello world!“等价。名字,这是我们很熟悉的概念,但是在数学和编程中,它不再叫名字,而是叫「变量」。为数据取一个名字,就是「定义一个变量」,而使用这个名字,叫做「变量的展开」或者「变量解引用」。很寻常的做法,一旦换了名目,就立刻令人觉得莫测高深了起来。名字之所以被视为变量,肯定不是因为老子说过「名可名,非恒名」。变量的存在,首先是为了便于数据的重复使用。譬如,一旦定义了变量 hello,便可以在命令中重复地使用它。其次,变量便于实现数据的复合,例如$ echo “I say: "$hello"“I say: “Hello world!“这条命令利用了双引号字串的宽松特性,实现了在字串中对变量进行解引用,从而起到了言语简约但意义完备的效果。但是,变量最重要的用途应该是执一发而动全身。如果有许多条命令使用了 hello 这个变量,当我对它指代的数据进行更换时,所有使用 hello 变量的命令皆会受到影响。注意,在定义变量时,等号两侧不能出现空白字符。例如$ my_var = “3"Bash 不会认为这条语句是在定义变量 my_var,反而会认为 my_var 是一条命令,而 = 和 “3” 是这条命令的输入数据。条件对于一个变量 foo,若不知它是否已定义,可在终端里唤它一下试试,$ echo $foo倘若 echo 只是输出一个空行,便可以确定 foo 未定义。上述命令中,echo 输出了空行,因此 foo 未定义。如果 foo 未定义,就给它一个定义,否则便对 foo 进行展开。像这样的任务,单凭查看 echo 的输出无法完成。不过,Bash 支持与「若……则……否则……」类似的语法。例如$ if [ “$foo” = "” ]; then foo=“FOO”; else echo $foo; fi或$ if [ “$foo” = "” ]> then> foo=“FOO”> else> echo $foo> fi[ “$foo” = "” ] 是一条命令,用于测试 “$foo” 与 "” 是否相同。if 可根据 [ “$foo” = "” ] 的结果控制 Bash 是执行 foo=“FOO” 还是执行 echo $foo。假设变量 foo 未定义,那么 [ “$foo” = "” ] 的结果是什么呢?为 0。假设变量 foo 已定义,那么 [ “$foo” = "” ] 的结果是什么呢?为非 0。如何得知 [ “$foo” = "” ] 的结果呢?显然这个结果不可能写在 stdout 里,否则我们可以从终端里看到这个结果,但事实上我们并不知道这个结果,而 if 却能知道。事实上,每条命令在被 Bash 执行后,都会给出一个称作「命令的退出状态」的结果。Bash 内定的变量 ? 便指代这个结果。因此,要查看一条命令的退出状态,只需在它结束后,立刻对 ? 进行展开。例如$ [ “$foo” = "” ]$ echo $?0[ “$foo” = "” ] 的退出状态为 0,意味着 “$foo” 与 "” 相同。倘若定义了 foo,$ foo=“FOO”$ [ “$foo” = "” ]$ echo $?1那么 [ “$foo” = "” ] 的退出状态为非 0,意味着 “$foo” 与 "” 不同。常用的测试命令由于 [ “$foo” = "” ] 是一条命令,因此必须注意,[ 与其后的字符之间至少要空出一格,否则这条命令便写错了,Bash 会拒绝执行。[ 和 ] 所囊括的文本称为表达式。在测试命令的表达式中, = 可用于比较两个字串是否相同,比较结果表现为测试命令的退出状态。类似地,!= 可用于比较两个字串是否不同。= 和 != 皆为双目运算符,即参与运算的对象是两个。对于字串,也有单目运算符,例如 -z,用于确定字串长度是否为 0。事实上,[ “$foo” = "” ] 与 [ -z “$foo” ] 等价。命令的输入/输出数据除了字串之外,还有文件。很多时候,也需要对文件进行一些测试。最为常用的是确定一份文件是否存在,单目运算符 -e 可满足这一要求。例如,$ if [ -e foo.txt ]; then rm foo.txt; else touch foo.txt; fi的含义是,若当前目录中存在文件 foo.txt,便将其删除,否则便创建一份空文件作为 foo.txt。rm 命令可用于删除文件或目录,touch 命令可用于创建给定文件名的空文件。类似命令有,-d 可用于确定文件或目录是否存在。-s 可用于确定文件存在并且内容为空。双目运算符 -nt 和 -ot 分别用于判断一个文件是否比另一个文件更新或更旧。Bash 提供了许多测试运算,详情可查阅 test 命令的手册,方法是:$ man 1 test之所以要查阅 test 命令的手册,是因为 [ 表达式 ] 只是 test 命令的一种简洁的写法。事实上,$ [ -e foo.txt ]与$ test -e foo.txt等价。在得知了 man 命令的存在之后,也许你会想查阅 rm,touch 等命令的手册。循环做 10 个俯卧撑。如何用 Bash 语言描述呢?有两种方式,一种是$ for ((i = 1; i <= 10; i++)); do echo “第 $i 个俯卧撑”; done也可以写为$ for ((i = 1; i <= 10; i++))> do > echo “第 $i 个俯卧撑”> done或$ for ((i = 1; i <= 10; i++)); do > echo “第 $i 个俯卧撑”> done执行这条命令后,继而终端便会显示第 1 个俯卧撑第 2 个俯卧撑第 3 个俯卧撑第 4 个俯卧撑第 5 个俯卧撑第 6 个俯卧撑第 7 个俯卧撑第 8 个俯卧撑第 9 个俯卧撑第 10 个俯卧撑另一种方式是$ i=1$ while ((i <= 10)); do echo “第 $i 个俯卧撑”; ((i++)); done也可写为$ i=1$ while ((i <= 10))> do> echo “第 $i 个俯卧撑”> ((i++))> done或$ i=1$ while ((i <= 10)); do> echo “第 $i 个俯卧撑”> ((i++))> done算术表达式在 for 和 while 之后出现的 ((…)) 称为算术表达式。算数表达式可独立存在,也可以与 if、for 和 while 配合使用。((a = 1)) 与 a=1 等价;算术表达式中的 = 两侧允许出现空格。算数表达式中的比较运算可用于 if 语句。例如$ if ((3 > 1)); then echo “Yes”; else echo “No”; fiYes$ if ((3 < 1)); then echo “Yes”; else echo “No”; fiNo注意,for 循环结构中的算术表达式是由三个算数表达式构成,即((表达式 1; 表达式 2; 表达式 3;))这种算术表达式只能在 for 循环结构中使用。表达式 1 在循环开始时被求值。表达式 2 是在每一轮循环之前被求值,求值结果可以控制循环停止的时机:若求值结果为 0,则循环停止,否则开始新一轮循环。表达式 3 是在每一轮循环结束后被求值。上一节的 while 循环便是对 for 循环表达式很好的解释。可以像变量展开那样获得算术表达式的求值结果。例如$ echo $((3 > 1))1$ echo $((3 < 1))0务必弄清楚上述算术表达式的求值结果与命令退出状态的区别。在 test 或 [ 命令中,数字之间的相等以及大小比较,可以用 -eq、-lt、-gt、-le、-ge,分别表示相等、小于、大于、不大于以及不小于。因此,在使用 test 命令时,要清楚是数字的比较还是字串的比较。函数与变量是数据的名字相似,函数是过程的名字。所谓过程,即按照时间顺序给出一组命令,让 Bash 依序执行每条命令。md5sum 命令可以算出给定文件的 MD5 码。例如,$ md5sum foo.jpg95e25f85ee3b71cd17c921d88f2326bf foo.jpg文件的 MD5 码,类似于我们的指纹。不同的人,指纹相同的概率很小。不同的文件,MD5 码相同的概率也很小。许多网站在收到用户上传的图片之后,会以图片文件的 MD5 码作为图片文件的名字,以此避免同样的图片存入数据库中,从而达成节省硬盘空间的目的。我们可以构造一个过程,将一份文件以它的 MD5 码重新命名,亦即我们要写一个函数。先来考虑,这个过程应当分为哪些步骤。首先可以使用 md5sum 算出文件的 MD5 码。但是观察上述 md5sum 的输出,需要设法将 MD5 码之后的空格以及文件名去除,但是要保留文件名的后缀(例如 .jpg),继而将剩余的 MD5 码以及文件名的后缀组合为文件名,用这个名字对文件进行重新命名。若实现上述过程,现有的 Bash 知识尚且不够。譬如,md5sum 命令输出的信息,如何将其作为数据,为其命名,从而得到一个变量?Bash 允许在变量的定义中临时启用自身的一个复本即子 Shell 去执行一些命令,而后将命令的输出到 stdout 的信息作为数据出现在变量的定义中。例如,$ md5_info="$(md5sum foo.jpg)"$ echo $md5_info95e25f85ee3b71cd17c921d88f2326bf foo.jpg(…) 便是子 Shell,括号之内的文本便是要交由 Bash 的子 Shell 执行的命令。若让子 Shell 所执行命令的输出作为数据,需要使用 $ 对子 Shell 予以展开。持有变量 md5_info 之后,可以使用 AWK 实现 MD5 码和文件名的后缀的组合。AWK 值得投入一些时间去学习它的基本用法,尤其是尚不知有什么好方法基于95e25f85ee3b71cd17c921d88f2326bf foo.jpg生成95e25f85ee3b71cd17c921d88f2326bf.jpg的此刻。以下命令可从 $md5_info 中提取 MD5 码和文件名称后缀:$ echo $md5_info | awk ‘{print $1}‘95e25f85ee3b71cd17c921d88f2326bf$ echo $md5_info | awk ‘BEGIN{FS=”.”} {print $NF}‘jpg利用 Bash 的子 Shell,便可以将上述两条命令合并到一个新的变量的定义中,即$ new_name="$(echo $md5_info | awk ‘{print $1}’).$(echo $md5_info | awk ‘BEGIN{FS=”.”} {print $NF}’)"$ echo $new_name95e25f85ee3b71cd17c921d88f2326bf.jpg上述 new_name 的定义很长,不便理解,可以像下面这样多用两个变量对较长的变量定义以予以拆分:$ md5_code="$(echo $md5_info | awk ‘{print $1}’)"$ suffix_name="$(echo $md5_info | awk ‘BEGIN{FS=”.”} {print $NF}’)"$ new_name="$md5_code.$suffix_name”$ echo $new_name95e25f85ee3b71cd17c921d88f2326bf.jpg有了 new_name 变量,接下来只需使用 mv 对 foo.jpg 重新命名:$ mv foo.jpg $new_name大功告成……可以将上述命令所形成的过程以函数对其命名了,即$ function rename_by_md5 {> md5_info="$(md5sum foo.jpg)"> md5_code="$(echo $md5_info | awk ‘{print $1}’)"> suffix_name="$(echo $md5_info | awk ‘BEGIN{FS="."} {print $NF}’)"> new_name="$md5_code.$suffix_name"> mv foo.jpg $new_name> }注:倘若你想亲手在终端里输入上述代码,不要忘记,$ 是一级命令提示符,> 是二级命令提示符,它们不必输入。rename_by_md5 便是 { 和 } 所包围的这组命令的名字。在终端里,可以像命令那样使用这个名字,$ rename_by_md5结果便会将当前目录的 foo.jpg 重新命名为 95e25f85ee3b71cd17c921d88f2326bf.jpg。如果接下来再次使用这个名字,Bash 便会抱怨,没有 foo.jpg 这个文件:$ rename_by_md5md5sum: foo.jpg: No such file or directorymv: cannot stat ‘foo.jpg’: No such file or directory这是因为 rename_by_md5 所指代的过程只能对 foo.jpg 文件重新命名。若已经对 foo.jpg 完成了重新命名,那么 foo.jpg 就不存在了,所以再次使用 rename_by_md5,便失效了。这样不好。函数应当能够变量那样,一经定义,便可重复使用。函数的重复使用,对于 rename_by_md5 意味着什么呢?意味着它所指代的过程不应当仅依赖 foo.jpg,而应当将这个过程所处理的文件名视为一个未知数。学过中学数学的我们应当很容易理解,函数的自变量就是未知数。上面定义的 rename_by_md5 里没有自变量,因此它虽然是函数,但实际上是一个常函数。在 Bash 语言里,函数的自变量不像我们在数学里所熟悉的 x、y、z 这些 ,而是 1、2、3……它们皆为变量,若获得它们指代的数据,需要用 $。掌握了这一知识,可对上述的 rename_by_md5 予以修改$ function rename_by_md5 {> md5_info="$(md5sum $1)"> md5_code="$(echo $md5_info | awk ‘{print $1}’)"> suffix_name="$(echo $md5_info | awk ‘BEGIN{FS="."} {print $NF}’)"> new_name="$md5_code.$suffix_name"> mv $1 $new_name> }亦即,将 rename_by_md5 原定义中出现那的 foo.jpg 全部更换为 $1,值得注意的是,这个 $1 与 awk 命令中的 $1 并不相同,而且 awk 命令也不会理睬前者。现在的 rename_by_md5 可以用于任何文件的重新命名,例如:$ rename_by_md5 foo.jpg$ rename_by_md5 bar.jpgrename_by_md5 的定义中的 $1 所指代的值便是在上述命令中的输入数据。这样的输入数据称为函数的参数。会写支持一个参数的函数,想必写支持两个、三个或更多个参数的函数,并不难吧?不难。脚本上一节所讨论的函数 rename_by_md5,是在一个终端里定义的。若终端关闭,再开启,这个函数的定义便不复存在。为了让它恒久地存在,可将其添加到 ~/.bashrc 文件里,然后$ source ~/.bashrc或者在下次启动终端之后,Bash 便知道 rename_by_md5 是已定义的函数。不过,另有更好的方法可令 rename_by_md5 恒久存在。为何放在 ~/.bashrc 中,Bash 便能得到函数的定义?不止如此,在上文中,我们也是在这份文件中定义了变量 PS1。想必在每次打开终端之时,终端里运行的 Bash 一定是读取了这份文件,并执行了文件中的命令。像 ~/.bashrc 这样的文件称为 Bash 脚本。我们也可以写与之类似的脚本,只是无法像 ~/.bashrc 那样特殊,在打开终端时就被 Bash 读取。不过也没必要那样特殊,因为已经有了 /.bashrc,而且我们也可以向它写入信息。自己写的 Bash 脚本,若它具备可执行权限,并且所在的目录位于 PATH 变量定义中的目录列表中,这份脚本文件的名字可以作为命令使用。写脚本的过程,通常称为脚本编程,意思是用脚本编写程序。或许你还不知道什么是文件的可执行权限以及 PATH 变量又是什么。变量是数据的名字。函数是过程的名字。那么,命令是谁的名字?是可执行文件的名字。何谓可执行文件?具有可执行权限的文件。例如,在 /tmp 目录创建一份空文件 foo,然后把它作为命令去执行:$ cd /tmp$ touch foo$ ./foocd 命令用于从当前目录跳转至指定目录。./foo 的意思是将当前目录(即 ./) 中的文件 foo 作为命令执行,结果得到的是 Bash 冷冰冰的拒绝:bash: ./foo: Permission denied此时,若查看 $? 的值,结果为 126:$ echo $?126按照 Bash 的约定,命令的退出状态为非 0,意味着命令对应的程序出错,为 0 则意味着命令对应的程序成功地完成了自己的任务。这一约定也决定了 if 语句是以命令的退出状态为 0 时表示条件为真,否则条件为假。接下来,可以用 ls 命令查看 foo 文件所具有的权限:$ ls -l foo-rw-r–r– 1 garfileo users 0 Dec 2 11:17 foo即使看不懂 ls 命令输出的信息的含义也没关系,接下来,使用 chmod 命令为 foo 增加可执行权限,然后再用 ls 命令查看它的权限:$ chmod +x foo$ ls -l foo-rwxr-xr-x 1 garfileo users 0 Dec 2 11:17 foo这次 ls 命令输出的结果与上一次有何不同?现在,再次执行 ./foo,并查看其退出状态:$ ./foo$ echo $?0虽然执行 ./foo 之后,终端什么也没有输出,但是这条命令的退出状态为 0,这表明 foo 是一个程序,并且成功地完成了自己什么也没有做的任务。现在,将 foo 文件从 /tmp 目录移动到 /.myscript 目录,若后者不存在,可使用 mkdir 命令创建。还记得 -d 吗?$ dest=/.myscript$ if [ ! -d $dest ]; then mkdir $dest; fi$ mv /tmp/foo $dest在此,可以复习一下条件语句。-d $dest 表示「$dest 存在」,其前加上 ! 便表示「$dest 不存在」,前面再出现 if 便表示「如果 $dest 不存在」。如果 $dest 不存在,当如何?「then mkdir $dest」。事实上,这里没必要使用条件语句。因为 mkdir 有一个选项 -p,倘若欲创建的目录已存在,-p 选项可以让 mkdir 停止创建这个目录的行为。因此,上述命令可等价地写为$ dest=/.myscript$ mkdir -p $dest$ mv /tmp/foo $dest现在,foo 文件位于 ~/.myscript 目录。只需将 /.myscript 添加到 PATH 所指代的目录列表,然后便可以将 foo 文件的名字 foo 作为命令使用:$ echo “PATH=/.myscript:$PATH” >> ~/.bashrc$ source /.bashrc$ echo $PATH/home/garfileo/.myscript:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin$ foo$ echo $?0在我的机器里,/.myscript 便是 /home/garfileo/.myscript,将这个目录添加到 PATH 所指代的目录列表之后,每当我在终端中输入 foo,Bash 便会从 $PATH 里的目录查找与命令 foo 同名的具有可执行权限的文件,然后将其作为程序运行。现在可以试着在 ~/.myscript 目录写一份名为 rename_by_md5 的脚本了!Here Document不过,在你打开一个文本编辑器,打算在 ~/.myscript 创建一份名为 rename_by_md5 的脚本文件之前,我觉得有必要给出使用 cat 命令写简单文件的方法:$ cat << ‘EOF’ > ~/.myscript/rename_by_md5> #!/bin/bash> function rename_by_md5 {> md5_info="$(md5sum $1)"> md5_code="$(echo $md5_info | awk ‘{print $1}’)"> suffix_name="$(echo $md5_info | awk ‘BEGIN{FS="."} {print $NF}’)"> new_name="$md5_code.$suffix_name"> mv $1 $new_name> }> rename_by_md5 $1> EOFBash 将这种写文件的方法称为 Here Document。命令中的第一个 EOF,用于设定文件的结束标志。第二个 EOF 意味着写文件过程至此终止。可以使用自己喜欢的标志代替 EOF。例如,$ cat << ‘很任性地结束’ > ~/.myscript/rename_by_md5> #!/bin/bash> … … …> rename_by_md5 $1> 很任性地结束注意,设定文件结束标志时,单引号字串形式的标志并非必须,但是单引号能够阻止 Bash 对正要写入文件的内容中的一些对它具有特殊含义的字符自作聪明地予以替换。执行上述写文件的命令之后,可以使用 cat 查看 ~/.myscript/rename_by_md5:$ cat ~/.myscript/rename_by_md5#!/bin/bashfunction rename_by_md5 { md5_info="$(md5sum $1)" md5_code="$(echo $md5_info | awk ‘{print $1}’)" suffix_name="$(echo $md5_info | awk ‘BEGIN{FS="."} {print $NF}’)" new_name="$md5_code.$suffix_name" mv $1 $new_name}rename_by_md5 $1四两拨千斤如果当前目录里有几千份图片文件需要用 rename_by_md5 命令进行重新命名,该如何做呢?现在,对于我们而言,只费吹灰之力而已,$ for i in ; do rename_by_md5 $i; done这个例子展示了 for 循环的另一种形式。 名曰「通配符」,表示当前目录所有的文件或目录。所以,ls * 可在终端里显示当前目录的所有文件。mv * /tmp 则可将当前目录里的所有文件移动到 /tmp 目录。for i in * 的意思是「对当前目录中的任一份文件 i」。对当前目录中的任一份文件 i 做什么?「rename_by_md5 $i」。陷阱与人类语言类似,稍有不甚,所说的话就会出现语病。Bash 语言亦如此。假设,当前目录有一份名为「a b.txt」的文件,若使用 md5sum 命令生成该文件的 MD5 码,命令若写成$ md5sum a b.txt便是错的。因为 md5sum 会以为我们让它为文件 a 和文件 b.txt 生成 MD5 码,而且 md5sum 的确支持这样做。对于名字含有空格的文件,在命令中,请务必使用双引号囊括起来:$ md5sum “a b.txt"如此便不会令 md5sum 产生误解。因此,上文中给出的md5_info="$(md5sum $1)“安全起见,应当将其写为md5_info="$(md5sum “$1”)“同理,$ for i in *; do rename_by_md5 $i; done应当写为$ for i in *; do rename_by_md5 “$i”; done有人已将 Bash 的常见陷阱总结成文,详见《Bash Pitfalls》,可待熟悉 Bash 语言并用它编写较为重要的程序时再行观摩。结语现在,请容许我华丽地退场:$ exit ...

December 3, 2018 · 8 min · jiezi

Git 使用指南

git init初始化git initgit clone克隆git clone [远端仓库] [目标文件夹名称(默认:远端仓库名)]git log查看历史提交:按时间先后顺序显示到[校验和]为止git log [校验和(默认:最新提交的校验和)]选项:–oneline:精简至单行版本–stat:增加文件修改信息-p:忽略空格引起的不同-w:忽略空格引起的不同–all –graph:用分支图显示所有历史提交–author="[作者]":显示作者包含[文本]的历史提交–grep="[文本]":显示内容包含[文本]的历史提交希望选项–all –graph成为命令git log的默认参数?在文件.bashrc中加入:git() { if [ “$1” = “log” ] then command git log –all –graph “${@:2}”; else command git “$@”; fi;}Enabling git log parameters by defaultgit shortlog查看历史提交:按作者分类显示git shortloggit show查看[校验和]提交的所有信息git show [校验和(默认:最新提交的校验和)]查看附注标签(Annotated Tag)的内容git tag [标签名]选项同git log。git status查看当前状态git statusgit diff查看尚未加入暂存区的改动git diffgit add将文件从工作区添加到暂存区git add [文件名1 文件名2 文件名3 …]将所有文件(文件.gitignore指示的除外)从工作区添加到暂存区git add .git rm将文件从暂存区移回到工作区git rm –cached [文件名1 文件名2 文件名3 …]git commit将暂存区提交到本地仓库会进入一个 vim 窗口,在此输入提交信息。所有以#开头的行都会被忽略。对于空行的问题无需纠结,因为 git 会忽略所有不必要的空行。git commit键入提交信息并提交git commit -m [提交信息]更新最近的提交(内容和信息)git commit –amend如修改了提交内容,建议先执行:git add .提交信息的书写规范(Udacity)Udacity Git Commit Message Style GuideThe goal is that each commit has a single focus. Each commit should record a single-unit change. Now this can be a bit subjective (which is totally fine), but each commit should make a change to just one aspect of the project.Conversely, a commit shouldn’t include unrelated changes - changes to the sidebar and rewording content in the footer. These two aren’t related to each other and shouldn’t be included in the same commit. Work on one change first, commit that, and then change the second one. That way, if it turns out that one change had a bug and you have to undo it, you don’t have to undo the other change too.The best way that I’ve found to think about what should be in a commit is to think, “What if all changes introduced in this commit were erased?”. If a commit were erased, it should only remove one thing.DO:do keep the message short (less than 60-ish characters)do explain what the commit does (not how or why!)DO NOT:do not explain why the changes are made (more on this below)do not explain how the changes are made (that’s what git log -p is for!)do not use the word “and"if you have to use “and”, your commit message is probably doing too many changes - break the changes into separate commitse.g. “make the background color pink and increase the size of the sidebar"git tag查看所有标签git tag添加轻便标签(Lightweight Tag)git tag [标签名] [校验和(默认:最新提交的校验和)]添加附注标签(Annotated Tag)会进入一个 vim 窗口,在此输入标签内容。所有以#开头的行都会被忽略。对于空行的问题无需纠结,因为 git 会忽略所有不必要的空行。git tag -a [标签名] [校验和(默认:最新提交的校验和)]键入附注标签的内容并提交git tag -a [标签名] [校验和(默认:最新提交的校验和)] -m [附注标签的内容]删除本地标签git tag -d [标签名1 标签名2 标签名3 …]删除指定远端标签git push [远端名] -d refs/tags/[标签名]git push [远端名] :refs/tags/[标签名]推送所有本地标签到远端git push [远端名] –tag从远端拉取所有标签到本地git fetch [远端名] –tag(在远端唯一的情况下,[远端名]可以省略。)git checkout切换git branch [校验和 / 标签名 / 分支名]恢复工作区中的某个文件到上次提交的状态git checkout – [文件名]恢复工作区中的全部文件到上次提交的状态git checkout – .git branch查看所有本地分支git branch查看所有远端分支git branch -r新建分支git branch [分支名] [校验和(默认:最新提交的校验和)]删除分支git branch -d [分支名1 分支名2 分支名3 …]删除分支的前提条件是:该分支不是当前分支。该分支上的最新提交已经合并到另一分支上(如需忽略本条件,请使用-D选项。警告:尚未合并的所有提交将会全部丢失!)。git merge将[分支名]合并到当前分支git merge [分支名]合并操作操作会因为冲突(conflict)而中止,条件是:参与合并的两个分支上,各存在一个提交,它们修改了同一个行范围。如需撤销合并操作,请使用命令git merge –abort。冲突的处理方法(Udacity)Merge Conflict Output ExplainedThe output that shows in the Terminal is:$ git merge heading-updateAuto-merging index.htmlCONFLICT (content): Merge conflict in index.htmlAutomatic merge failed; fix conflicts and then commit the result.Notice that right after the git merge heading-update command, it tries merging the file that was changed on both branches (index.html), but that there was a conflict. Also, notice that it tells you what happened - “Automatic merge failed; fix conflicts and then commit the result”.Remember our good friend git status? Well he’ll come in really handy when working with merge conflicts.$ git statusOn branch masterYou have unmerged paths. (fix conflicts and run “git commit”) (use “git merge –abort” to abort the merge)Unmerged paths: (use “git add <file> …” to mark resolution) both modified: index.htmlThe git status output tells us to that the merge conflict is inside index.html. So check out that file in your code editor!Merge Conflict Indicators Explanation<<<<<<< HEAD everything below this line (until the next indicator) shows you what’s on the current branch||||||| merged common ancestors everything below this line (until the next indicator) shows you what the original lines were> > > > > > > heading-update is the ending indicator of what’s on the branch that’s being merged in (in this case, the heading-update branch)Resolving A Merge ConflictGit is using the merge conflict indicators to show you what lines caused the merge conflict on the two different branches as well as what the original line used to have. So to resolve a merge conflict, you need to:choose which line(s) to keepremove all lines with indicatorsFor some reason, I’m not happy with the word “Crusade” right now, but “Quest” isn’t all that exciting either. How about “Adventurous Quest” as a heading?!?Commit Merge ConflictOnce you’ve removed all lines with merge conflict indicators and have selected what heading you want to use, just save the file, add it to the staging index, and commit it! Just like with a regular merge, this will pop open your code editor for you to supply a commit message. Just like before, it’s common to use the provided merge commit message, so after the editor opens, just close it to use the provided commit message.And that’s it! Merge conflicts really aren’t all that challenging once you understand what the merge conflict indicators are showing you.If a merge conflict occurs in a file and you edit the file, save it, stage it, and commit it but forget to remove the merge conflict indicators, Git will commit the lines with the merge conflict indicators, because they’re just regular characters.git revertgit revert [校验和]设[校验和]提交的前一个提交为PREVIOUS,修改的行范围是range。该命令的作用是回滚range范围内的修改,将会产生一个新的提交。回滚操作操作会因为冲突而中止,条件是:如果range范围内的某一部分在[校验和]提交的某个后续提交中被修改。如需撤销回归操作,请使用命令git revert –abort。冲突的处理方法与git merge相同。git resetgit reset [校验和]该命令的作用是回到[校验和]提交的状态,即撤销[校验和]提交的后续提交的所有修改modification。选项:–mixed:将modification移入工作区(默认)–soft:将modification移入暂存区–hard:忽略(删除)modification执行git reset之后,[校验和]提交的后续提交未被删除。可以通过下列命令查看这些不属于任何分支的提交:git reloggit fsck –unreachable –no-reflog(具体使用方法详见后文。)可以通过切换到提交、新建分支、合并到现有分支的方法恢复这些提交(即回滚git reset操作)。相对引用(Udacity)Relative Commit ReferencesYou already know that you can reference commits by their SHA, by tags, branches, and the special HEAD pointer. Sometimes that’s not enough, though. There will be times when you’ll want to reference a commit relative to another commit. For example, there will be times where you’ll want to tell Git about the commit that’s one before the current commit…or two before the current commit. There are special characters called “Ancestry References” that we can use to tell Git about these relative references. Those characters are:^ – indicates the parent commit~ – indicates the first parent commitHere’s how we can refer to previous commits:the parent commit – the following indicate the parent commit of the current commitHEAD^HEADHEAD1the grandparent commit – the following indicate the grandparent commit of the current commitHEAD^^HEAD2the great-grandparent commit – the following indicate the great-grandparent commit of the current commitHEAD^^^HEAD3The main difference between the ^ and the ~ is when a commit is created from a merge. A merge commit has two parents. With a merge commit, the ^ reference is used to indicate the first parent of the commit while ^2 indicates the second parent.The first parent is the branch you were on when you ran git mergeThe second parent is the branch that was merged in.git remote查看所有远端仓库的 URLgit remote -v关联到远端仓库:[远端仓库的别名]通常被设为origingit remote add [远端仓库的别名] [远端仓库的URL]取消与远端仓库的关联git remote remove [远端仓库的别名]git push这里仅讨论同时满足下列条件的情形:本地分支和远程分支同名。单一远端,通常是 origin。将本地分支推送到远端(完整命令)git push [远端名] [本地分支] [远端分支]省略[远端分支]的情形:将[本地分支]推送到“与之存在追踪关系的”远程分支(通常两者同名),如果该远程分支不存在,则会被新建。git push [远端名] [本地分支]可以为该命令添加-u参数,表示将远端分支设为[本地分支]的 upstream branch。git push -u [远端名] [本地分支]执行上述命令后,就可以在切换到相应分支git checkout [本地分支]之后,直接使用命令git push了。git push选项–all表示推送所有分支到远端,通常添加选项-u以设定所有本地分支的 upstream branch。git push -u –all删除远端分支git push [远端名] -d [远端分支]git push [远端名] :[远端分支]如果出现各种原因导致命令git push不能成功执行(例如:删除并重建本地分支、合并本地的commit),可以使用选项-f进行强行(覆盖)推送。请注意:不要在多人合作的远端分支中慎用,原因参见:团队开发里频繁使用 git rebase 来保持树的整洁好吗?git 如何撤销一次remote的master commit?git push -fgit pull这里仅讨论同时满足下列条件的情形:本地分支和远程分支同名。单一远端,通常是 origin。该命令的用途是:取回远程主机某个分支的更新,再与本地的指定分支合并。因为远端分支 origin/master 通常是本地分支 master 的更新,所以合并操作只是移动 master 引用的位置,通常难以察觉。将远端分支拉取到本地(完整命令,多用于创建和远端分支对应的本地分支)。git pull [远端名] [远端分支]:[本地分支]在满足下列条件的前提下,可以直接使用命令git pull:已使用git checkout将该对应的本地分支切换为当前分支。该分支的 upstream branch 已设置。具体方法为git branch –set-upstream-to=origin/[远端分支] [本地分支]git pullgit fetch这里仅讨论同时满足下列条件的情形:本地分支和远程分支同名。单一远端,通常是 origin。该命令的用途是:取回远程主机某个分支的更新,但是不与本地分支合并。git fetch [远端名] [远端分支]:[本地分支]在满足下列条件的前提下,可以直接使用命令git pull:已使用git checkout将该对应的本地分支切换为当前分支。该分支的 upstream branch 已设置。具体方法为git branch –set-upstream-to=origin/[远端分支] [本地分支]git fetch区别于命令git pull:命令git fetch让用户决定怎样合并远端分支和本地分支。命令git pull自动进行分支合并,在某些情况下可能无法成功执行。problem本地分支和远端分支被同时更新。更新前: master |remote a – 3 – d – f – e – 7============================================================ origin/master |local a – 3 – d – f – e – 7 | master更新后: master |remote a – 3 – d – f – e – 7 – 8============================================================ origin/master |local a – 3 – d – f – e – 7 – b | masterSolution首先先使用命令git fetch,获取远端分支更新。 master |remote a – 3 – d – f – e – 7 – 8============================================================ origin/master | 8 /local a – 3 – d – f – e – 7 – b | master再使用命令git merge origin/master,将远端分支合并到本地分支。 master |remote a – 3 – d – f – e – 7 – 8============================================================ origin/master | 8 / \local a – 3 – d – f – e – 7 – b – 4 | master最后使用命令git push,推送本地分支到远端分支。 master | 8 | / \ |remote a – 3 – d – f – e – 7 – 8 – 4============================================================ origin/master | 8 | / \ |local a – 3 – d – f – e – 7 – b – 4 | master其他https://www.worldhello.net/go…查看(90天内的)操作历史git reflog查看不可达的(unreachable)对象,包括但不限于:暂存区操作时引入的临时对象(以unreachable blob开头)不在分支上的提交(以unreachable commit开头)git fsck –unreachable –no-reflog清除不可达的对象(通常为:暂存区操作时引入的临时对象)如果对象git reflog命令中可见,那么git prune就不能清除它。若一定要清除,则需先使用git reflog expire –expire=now –all清除所有历史(不推荐)。git prune优化仓库git gc ...

October 26, 2018 · 7 min · jiezi