脚本文件的一些技巧收集

By | 2016-07-31

目录

最近因为撸各种编译脚本, 碰到了不少脚本文件的问题, 而且很多东西你搜索都不知道该用什么关键词,
于是留个文备忘一下

参数相关

获取脚本文件所在目录

# sh
echo $(cd "$(dirname "$0")"; pwd)
rem bat
echo %~dp0

获取脚本文件自身

# sh
echo $0
rem bat
echo %0

获取所有参数

# sh
echo "$@"

# 注意, 传递时需要加引号, 否则带空格的参数会被拆为两个参数
sh other.sh "$@"
rem bat
echo %*

rem 注意, 传递时不能加引号, 否则会视为一个参数
other.bat %*

移动到下一个参数

# sh
echo $0
shift
echo $0

# 注意, shift 会影响 $@
echo $@
rem bat
echo %0
shift
echo %0

rem 注意, shift 不会影响 %*
echo %*

获取参数个数

# sh
echo $#
rem bat
set argC=0
for %%x in (%*) do set /A argC+=1
echo %argC%

除第一个参数外传递所有参数

# sh
# 从第二个开始
shift
sh other.sh "$@"
# 从第三个开始
shift 2
sh other.sh "$@"
# 注意: 必须使用 "$@", 不能先保存为变量再传递, 否则带空格的参数会被拆分为两个

# 注意: 以上方法会丢弃 shift 之前的参数, 使用前记得保存
# 上述是比较通用的实现方式, 各种 shell 都能用, 不介意兼容性的话, 也可以用这个
# 从第二个开始
${@:2}
# 从第三个开始
${@:3}
rem bat
# 注意, 必须要 enabledelayedexpansion, 否则 for/if 中的 !ALL_PARAMS! 变量无法正常展开
setlocal enabledelayedexpansion

rem 从第二个开始
set I=0
for %%x in (%*) do (
    set /a I=I+1
    if !I! geq 2 (set ALL_PARAMS=!ALL_PARAMS! %%x)
)
other.bat %ALL_PARAMS%

rem 从第三个开始
set I=0
for %%x in (%*) do (
    set /a I=I+1
    if !I! geq 3 (set ALL_PARAMS=!ALL_PARAMS! %%x)
)
other.bat %ALL_PARAMS%

变量判断相关

判断变量是否为空

# sh
if test "x$YourParam0" = "x"\
    || test "x$YourParam1" = "x"\
    || test "x$YourParam2" = "x"\
    ; then
    echo show usage
    exit 1
fi
rem bat
if not defined YourParam0 goto :usage
if not defined YourParam1 goto :usage
if not defined YourParam2 goto :usage
goto :run
:usage
echo show usage
exit /b 1
:run

变量值比较

# sh
if test "x-$A" = "x-xxx" ; then
    echo "A == xxx"
fi
if ! test "x-$A" = "x-xxx" ; then
    echo "A != xxx"
fi

if test $A -eq $B ; then
    echo "A == B"
elif test $A -ne $B ; then
    echo "A != B"
elif test $A -gt $B ; then
    echo "A > B"
elif test $A -lt $B ; then
    echo "A < B"
elif test $A -ge $B ; then
    echo "A >= B"
elif test $A -le $B ; then
    echo "A <= B"
fi
rem bat
if "%A%" == "xxx" (echo A == xxx)
if not "%A%" == "xxx" (echo A != xxx)

if %A% equ %B% (echo A == B)
if %A% neq %B% (echo A != B)
if %A% gtr %B% (echo A > B)
if %A% lss %B% (echo A < B)
if %A% geq %B% (echo A >= B)
if %A% leq %B% (echo A <= B)

变量值替换

# sh
MyVar=`echo $MyVar | sed -e "s/ReplaceFrom/ReplaceTo/g"`
rem bat
set MyVar=!MyVar:ReplaceFrom=ReplaceTo!

变量数值计算

# sh
RESULT=$((A+B))
rem bat
set /a RESULT=A+B

变量截取相关

截取父路径

# sh
SRC=a/b/c.txt
RESULT=${SRC%[/\\]*}
echo $RESULT
rem bat
set SRC=a/b/c.txt
for %%a in (%SRC%\..) do set RESULT=%%~fa
echo %RESULT%

截取文件名

# sh
SRC=a/b/c.txt
RESULT=${SRC##*[/\\]}
echo $RESULT
rem bat
set SRC=a/b/c.txt
for %%a in (%SRC%) do set RESULT=%%~nxa
echo %RESULT%

截取除扩展名外的文件名

# sh
SRC=a/b/c.txt
RESULT=$(basename ${SRC%.*})
echo $RESULT
rem bat
set SRC=a/b/c.txt
for %%a in (%SRC%) do set RESULT=%%~na
echo %RESULT%

截取扩展名

# sh
SRC=a/b/c.txt
RESULT=${SRC##*.}
echo $RESULT
rem bat
set SRC=a/b/c.txt
for %%a in (%SRC%) do set RESULT=%%~xa
set RESULT=%RESULT:~1,999%
echo %RESULT%

绝对路径转换为相对路径

# sh
RESULT=`realpath --relative-to="$REF_PATH" "$ABS_PATH"`
rem bat
@echo off
setlocal

set WORK_DIR=%~dp0
set ABS_PATH=%~2%
set REF_PATH=%~3%

if not defined ABS_PATH goto :usage
if not defined REF_PATH goto :usage
goto :run
:usage
echo usage:
echo   path_abs2rel.bat RESULT ABS_PATH REF_PATH
exit /b 1
:run

for %%i in (%REF_PATH%) do set REF_PATH_TMP=%%~fi
for %%i in (%ABS_PATH%) do set ABS_PATH_TMP=%%~fi

if not exist "%REF_PATH_TMP%" (
    RESULT=%ABS_PATH_TMP%
    goto :return_result
)
if not exist "%ABS_PATH_TMP%" (
    RESULT=%ABS_PATH_TMP%
    goto :return_result
)

set SAME_PATH=%REF_PATH_TMP%
set SAME_PATH_MATCH=0
:same_path_loop
call set "MATCH_TMP=%%ABS_PATH_TMP:%SAME_PATH%=%%"
if not "%MATCH_TMP%" == "%ABS_PATH_TMP%" (
    set SAME_PATH_MATCH=1
    goto :same_path_loop_end
)
set SAME_PATH_OLD=%SAME_PATH%
for %%a in (%SAME_PATH%\..) do set SAME_PATH=%%~fa
if "%SAME_PATH%" == "%SAME_PATH_OLD%" goto :same_path_loop_end
goto :same_path_loop
:same_path_loop_end

if not %SAME_PATH_MATCH% == 1 (
    RESULT=%ABS_PATH_TMP%
    goto :return_result
)

set RESULT=

call set "REF_PATH_TMP=%%REF_PATH_TMP:%SAME_PATH%=%%"
:abs_parent_loop
if "x%REF_PATH_TMP%" == "x" (
    set RESULT=.
    goto :abs_parent_loop_end
)
set REF_PATH_TMP_OLD=%REF_PATH_TMP%
for %%a in (%REF_PATH_TMP%\..) do set REF_PATH_TMP=%%~fa
if "%REF_PATH_TMP%" == "%REF_PATH_TMP_OLD%" goto :abs_parent_loop_end
if "x%RESULT%" == "x" (
    set RESULT=..
) else (
    set RESULT=%RESULT%\..
)
goto :abs_parent_loop
:abs_parent_loop_end

call set "RESULT_TMP=%%ABS_PATH_TMP:%SAME_PATH%=%%"
set RESULT=%RESULT%%RESULT_TMP%

:return_result
endlocal & set "%1=%RESULT%"

相对路径转换为绝对路径

# sh
RESULT=$(cd -- "$RELATIVE_PATH" && pwd)
rem bat
for %%i in (%RELATIVE_PATH%) do set RESULT=%%~fi

返回值判断相关

函数调用和返回值

# sh
function add()
{
    echo "$1 + $2 = $(($1+$2))"
    return 0
}
RESULT=`add 2 3`
echo "run success: $?"
echo "run result: $RESULT"
rem bat
call :add RESULT 2 3
echo run success: %errorlevel%
echo run result: %RESULT%
goto :EOF

:add
setlocal
set /a _RESULT=%2+%3
endlocal & set "%1=%_RESULT%"
exit /b 0

退出当前脚本

N 为任意整数

# sh
exit N
rem bat
exit /b N

获取返回值和判断返回值

# sh
sh xxx.sh
result = "$?"
if test "$result" = "0"; then
    echo success
fi
if test ! "$result" = "0"; then
    echo failed
fi
rem bat
call xxx.bat
set result = %errorlevel%
if "%result%" == "0" (
    echo success
)
if not "%result%" == "0" (
    echo failed
)

rem 如果在 if 中使用 error level, 需要这样:
setlocal enabledelayedexpansion
if "1" == "1" (
    call xxx.bat
    if "!errorlevel!" == "0" (
        echo success
    )
)

执行外部命令并获取输出

# sh
result = `git config user.name`
rem bat
for /f "delims=" %%a in ('git config user.name') do @set result=%%a

rem 但是, 上述只能处理只有单行输出的命令, 对于多行输出命令, 必须用更奇葩的方式:
setlocal enabledelayedexpansion
rem 注意这边必须要有两个换行
(set NLM=^

)
set NL=^^^%NLM%%NLM%^%NLM%%NLM%
(
    for /f "tokens=*" %%a in ('dir /b') do (
        if not defined result (
            set result=%%a
        ) else (
            set result=!result!%NL%%%a
        )
    )
) >nul 2>&1
rem 注意 errorlevel 无法获取到命令的错误码
rem echo %errorlevel%
rem 注意这个感谈号, 以及 enabledelayedexpansion
echo !result!

管道相关

忽略输出和错误信息

# sh
sh xxx.sh >/dev/null 2>&1
rem bat
call xxx.bat >nul 2>&1

rem ignore error only
call xxx.bat 2>nul

输出和错误信息都输出到同一个文件

# sh
sh xxx.sh >filepath.log 2>&1
rem bat
call xxx.bat >filepath.log 2>&1

其它

判断文件或目录是否存在

# sh
if test -e "filepath"; then
    echo exist
fi
rem bat
if exist "filepath" (
    echo exist
)

删除空目录

# sh
find "$DST_PATH" -depth -type d -empty -exec rm -rf {} ';' >/dev/null 2>&1
rem bat
rem 这种方式在 cygwin 下无法使用, 因为 cygwin 里面也有 sort
for /f "delims=" %%a in ('dir /s/b/ad "%DST_PATH%" ^| sort /r') do rd /q "%%a" 2>nul

rem 这种方式在 cygwin 下也能够正常, 但是一次只能删除目录层次最深的一级, 最简单粗暴的方式就是多执行几遍
for /d /r "%DST_PATH%" %%a in (*) do dir /b/a "%%a" | findstr . >nul || rmdir "%%a"
for /d /r "%DST_PATH%" %%a in (*) do dir /b/a "%%a" | findstr . >nul || rmdir "%%a"
for /d /r "%DST_PATH%" %%a in (*) do dir /b/a "%%a" | findstr . >nul || rmdir "%%a"

获取当前时间戳

# sh
RESULT=$(date +%s)
echo $RESULT
rem bat
call :GetUnixTime RESULT
echo %RESULT%
exit /b 0

:GetUnixTime
setlocal enableextensions
for /f %%x in ('wmic path win32_utctime get /format:list ^<nul ^| findstr "="') do (
    set %%x)
set /a z=(14-100%Month%%%100)/12, y=10000%Year%%%10000-z
set /a ut=y*365+y/4-y/100+y/400+(153*(100%Month%%%100+12*z-3)+2)/5+Day-719469
set /a ut=ut*86400+100%Hour%%%100*3600+100%Minute%%%100*60+100%Second%%%100
del /s/q TempWmicBatchFile.bat >nul 2>&1
endlocal & set "%1=%ut%" & goto :EOF

获取文件大小

# sh
RESULT=`du -b "$FILE_PATH" | awk '{print $1}'`
echo $RESULT
rem bat
for /f "delims=" %%a in ("%FILE_PATH%") do set RESULT=%%~za
echo %RESULT%

其他注意点

sh

shell 差异

撸 sh 脚本时最好尽量符合 POSIX 标准, 目前碰到的主要差异有,
bash 和 dash 的差异, GNU 和 BSD 部分命令的差异

中括号变量比较

if [[ $my_var = "" ]]; then
    echo "empty"
fi

中括号似乎并不是所有 shell 都支持, 推荐的做法是用 test

if test "x$my_var" = "x" ; then
    echo "empty"
fi

对于目录的处理

以 cp 为例, 建议这么做

cp -r src0/src1/. dst0/dst1/

注意 src 末尾的 /. 和 dst 末尾的 /,
不按照这么写的话, 在不同 shell 下可能会有意外的结果

bat

转义

没错, 批处理就是能把这么基本的东西也搞屎, 关于转义可以参考 这里,
这边也抄一份以防万一:

%   %%   May not always be required in doublequoted strings, just try
^   ^^   May not always be required in doublequoted strings, but it won't hurt
&   ^&
<   ^<
>   ^>
|   ^|
'   ^'   Required only in the FOR /F "subject" (i.e. between the parenthesis), unless backq is used
`   ^`   Required only in the FOR /F "subject" (i.e. between the parenthesis), if backq is used
,   ^,   Required only in the FOR /F "subject" (i.e. between the parenthesis), even in doublequoted strings
;   ^;
=   ^=
(   ^(
)   ^)
!   ^^!  Required only when delayed variable expansion is active
"   ""   Required only inside the search pattern of FIND
\   \\   Required only inside the regex pattern of FINDSTR
[   \[
]   \]
"   \"
.   \.
*   \*
?   \?

引号

set v="123"
set v1="%v%"
rem 最终 v1 会是 ""123""
rem 要确保正确的话, 最好这样:
set v2="%~v%"

建议设置

建议批处理文件开头都添加以下内容:

rem 默认关掉恼人的 echo
@echo off

rem 不加的话, 脚本内的变量会影响外部变量, 又是一个蛋疼的默认行为
setlocal

rem 如果想在 for 循环内修改并访问变量, 必须添加该设置,
rem 并且使用 !VAR! 访问变量, 否则变量修改不会生效
setlocal enabledelayedexpansion

转载请注明来自: http://zsaber.com/blog/p/123

既然都来了, 有啥想法顺便留个言呗? (无奈小广告太多, 需审核, 见谅)

发表回复

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