error

虽然不把shell当作主力的编程语言,但是在任务中,难免不会用到shell,尤其是拿shell来做为各种程序之间的粘合剂,或者那shell直接做一些简单的任务,或者定时任务。

shell功能强大,代码简省,如果写的合适,shell的性能不输其他专门的脚本语言,但是呢,遇到问题,也是头疼。故,本文的目的是,尽可能多的记录工作中遇到的问题。方便,自己。

问题列表

{0,1}解析

sh myshell.sh 最好自己指定使用哪个shell,可能不同的shell之间略微有差别。

for db in {0,1};do
    for table in `seq 0 1`;do
        outsql=marked_ship_$db_$table.sql
        echo "ordercenter_order_tag_"$db marked_ship_$table to $outsql
    done
done

宿主机上直接执行sh mysh.sh 实际/usr/bin/sh

ordercenter_order_tag_0 marked_ship_0 to marked_ship_0.sql
ordercenter_order_tag_0 marked_ship_1 to marked_ship_1.sql
ordercenter_order_tag_1 marked_ship_0 to marked_ship_0.sql
ordercenter_order_tag_1 marked_ship_1 to marked_ship_1.sql

docker mysql 执行sh mysh.sh 实际/bin/sh

ordercenter_order_tag_{0,1} marked_ship_0 to marked_ship_0.sql
ordercenter_order_tag_{0,1} marked_ship_1 to marked_ship_1.sql

直接bash执行 方一致。貌似因为执行的

echo $SHELL
which sh   # 查看哪个sh

crontab -e出错

问题类似于下面这个文章:

http://blog.sina.com.cn/s/blog_633685790102vms0.html

报错的时候:

crontab -e

no crontab for dxt - using an empty one
/tmp/crontab.XXXX9MaMQk: 权限不够

大概是因为/tmp文件夹的权限问题。故,直接:

chmod 777 /tmp

定时任务,我个人觉得特别难编写,主要是因为,1、执行的时候,先cd到目录,最稳妥,防止引入了相对变量的文件路径。2、加上环境变量,尤其是PATH,有时,居然连/usr/local/bin都不在环境变量中。故,最好自己设置一下。3、测试,测试的时候,跳到cd /路径,再执行,看看能否正常执行。

heredoc

为中文变量复制,正确的用法如下

query_sql=$(cat <<EOL
SELECT
	COUNT (DISTINCT ship_id)
FROM
	tb_scan
WHERE
	ins_db_tm >= '2021-09-24'
AND ins_db_tm < '2021-09-25'
AND scan_typ = '14'
AND scan_site = '';
EOL
)

echo $query_sql

说明:EOL的括号,要另起一行,避免导致结束符号失效。另外,echo,输出的内容,换行符号预产被去掉了,即只有一行。

heredoc要配合cat才能使用,query_sql=<<EOL,直接为变量赋值的格式,结果,输出的是一个空行。

query_sql=$(
    echo $query_sql |
    sed "s/#START_DATE/$START_DATE/g"|
    sed "s/#END_DATE/$END_DATE/g" |
    sed "s/#SCAN_SITE/$SCAN_SITE/g"
)

还能按如下的方式,来拼接管道。

cat <<'EOF' | sed 's/i/m/g' > linuxidc.txt

ssh时,使用heredoc

ssh -T user@host.com << EOF

文件名中有空格

for each in  *.flv;do
    grep "$each" downlist.txt > /dev/null
    if [ $? -ne 0 ];then
         echo $each
         sz "$each"
    fi
done

说明如下:

  • in后面直接跟模糊查找的名称,这样,而不是使用两个反引号或者$(ls *.flv)等符号,这是因为,in的属性,会默认将包含空格的部分,切分成两部分,而文件名中,正好又包含了空格等符号。

  • grep命令中,使用双引号包裹住each变量,这是因为,如果不包裹,则each变量中因包含空格,而被分隔成两部分。下面的sz命令相同。

这应该说是shell中针对空格中的坑。

echo * 问题

问题描述:

SQL="
copy (SELECT * FROM tu_doc_info WHERE dtime >= '${DAY_AGO_10}' and doc_sn = '${SHIP_ID}' order by dtime ) to '${TMPDIR}${SHIP_ID}_ludan.csv' with csv header;
"

SQL=$(cat <<EOL
copy (SELECT * FROM tu_doc_info WHERE dtime >= '${DAY_AGO_10}' and doc_sn = '${SHIP_ID}' order by dtime ) to '${TMPDIR}${SHIP_ID}_ludan.csv' with csv header;
EOL
)

START='*'
SQL=$(cat <<EOL
copy (SELECT ${START} FROM tu_doc_info WHERE dtime >= '${DAY_AGO_10}' and doc_sn = '${SHIP_ID}' order by dtime ) to '${TMPDIR}${SHIP_ID}_ludan.csv' with csv header;
EOL
)

echo $SQL

有如上的3种写法,在echo的时候,sql的*都会被当前执行shell的工作路径下的,文件列表所替代。这本来是echo的一个强大特色。

但是,不要慌,echo出来的,是被echo加工了,*交给psql执行的时候,不会被替换成文件列表。

解决方式非常简单:如下:

echo "$SQL"

原因:echo 后面的变量,如果缺少了双引号包裹,则,变相的,拆分成了n个参数,echo * 确实会输出文件列表。

命令 * 错误2

问题基本上等同上个问题,但是隐藏性更深一些。先说如何正确的调用:

#!/bin/bash

day=$1
gp_file_name=$2
csv_path=$3
csv_name="$4"
for each in `ls $csv_path/$day/$csv_name`;do
    echo $each
    psql -d  develop   -h  localhost   -U gpadmin  -p 5432  -c "\copy $gp_file_name from $each with csv";
    if [ $? -ne 0 ];then
        echo "error: import $each"
    fi
done

正确调用:

./day_file_without_header.sh 20211024_2 cyf_etl_dta_20211026 /etl/dta "tu_doc_info*.csv"

错误调用:

./day_file_without_header.sh 20211024_2 cyf_etl_dta_20211026 /etl/dta tu_doc_info*.csv

区别就是,最后一个带*好的参数,其实在调用命令时,参数应该被bash给解析了,解析成当前目录下的文件。

有点像我常用的命令行参数:

sz  *.flv

其实,就是bash在我调用参数的时候,帮我给解析了。

总结:调用命令行参数的时候,当我们希望bash帮我们解析参数的时候,做额外的工作时,我们就不加引号,需要包含我们的参数的时候,不希望bash做额外工作时,就加引号(单双均可)保护。

ls "$csv_path/$day/$csv_name" 这种被保护起来了,bash就不会帮我们做额外的工作,但又不是我们想要的结果。

单引号保护

./crud_zh.sh

#!/bin/bash
# 时间:2022-02-21 10:13
# 描述:yii2自动生成的代码,汉化
if [ -z "$1" ];then
    echo "$0 ../cloud/backend/views/netrange/*.php"       
    exit
fi



sed -i 's#<h1><?= Html::encode($this->title) ?></h1>##' $1
sed -i 's#Update#更新#' $1
sed -i 's#Delete#删除#' $1
sed -i 's#Are you sure you want to delete this item?#您确定要删除该条记录吗?#' $1
sed -i 's#Create#新增#' $1
sed -i 's#Save#保存#' $1

调用

./crud_zh.sh '../cloud/backend/views/warning-order/*.php'

错误调用

./crud_zh.sh ../cloud/backend/views/warning-order/*.php

说明:没有单引号保护时,crud_zh.sh,将*解析调,那么,*被解析成多个具体的文件,可能传入多个参数到./crud_zh.sh,而到sed的命令中,$1只是第一个文件。

unix换行

两种方式,一种是使用vim,设置并保存,另外一种,更简单一些,使用sed批量替换。

出现的另外一种原因是:git设置,会自动的转换换行符号。切记。

sed -i 's/\r$//' for_lua.sh

# vim
set ff=unix  
set fileformat=unix  # 全名