1. 前言本文中,记录使用shell循环读取文本的常用方法和技巧。
2. 读取内容并拼接2.1. 需求描述已知mobile.txt为:
1 2 haojin 17625160000 voidking 17625160001
需求:根据 mobile.txt 中的内容拼接成SQL,修改不同用户的手机号。例如:
1 update user set mobile= "17625160000" where name= "haojin";
2.2. 脚本实现脚本:
1 2 3 4 5 6 7 8 #!/bin/bash grep -v "^$" mobile.txt | while read line do name=`echo $line | awk '{print $1}' ` mobile=`echo $line | awk '{print $2}' ` echo "update user set mobile=\"${mobile} \" where name=\"${name} \";" done
PS:不能使用for line in cat 'mobile.txt'
,因为这种方法会按照空格或换行切分文本。
3. 读取IP和端口并测试连通性3.1. 需求描述使用while read line
循环读取文本的方法,对于单纯的拼接需求是没有问题的。但是,如果涉及到输入缓存,就会有问题了。
已知service.txt内容为:
1 2 3 127.0.0.1 80 127.0.0.1 8080 192.168.56.101 8080
需求:探测service.txt中每个服务的连通性,并记录结果。
3.2. 错误实现我们用while read line
方式实现脚本:
1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash : > detectresult.txt cat service.txt | while read linedo ip=$(echo $line | awk '{print $1}' ) port=$(echo $line | awk '{print $2}' ) res=$(nc -w 2 -v $ip $port ) echo "$res " >> detectresult.txt done
执行脚本后,我们发现结果文件中只有一条结果!这就不符合预期了。 这是因为while使用重定向机制,while read line
一次性将文件信息读入输入缓存,并按行赋值给变量line,直到输入缓存数据为空。而刚好nc、telnet、ssh等命令,会读取输入缓存中的所有数据,这就导致输入缓存被清空了,while循环结束。
3.3. 更正实现解决办法:使用重定向将 /dev/null (一个特殊的设备文件,会丢弃所有写入其中的数据,并在读取时立即结束文件)作为输入传递给 nc。
1 res=$(nc -w 2 -v $ip $port < /dev/null)
实际上,这意味着 nc 会立即得到一个“文件结束”(EOF)的指示,无需等待用户输入。这样做可以让 nc 在尝试连接后立即终止,不会保持等待用户数据输入。
或者,改用 -zv
参数使得nc命令不需要输入。因为这里探讨的是 nc、telnet、ssh 这类等待输入的命令在while循环中的通用解决办法,所以不使用-zv
参数。
因此,正确的脚本应该改成:
1 2 3 4 5 6 7 8 9 10 #!/bin/bash cat /dev/null > detectresult.txtwhile read linedo ip=$(echo $line | awk '{print $1}' ) port=$(echo $line | awk '{print $2}' ) res=$(nc -w 2 -v $ip $port < /dev/null) echo "$res " >> detectresult.txt done < service.txt
4. 读取内容并用户交互4.1. 需求描述使用while read line
循环读取文本的方案,已经具备了较好的通用性,可以应对大多数场景。 但是,如果循环读取时,还需要和用户进行交互,那么就不适用了,下面看一个例子。
已知applist.txt内容为:
1 2 3 app1 running hba app2 stop hbe app3 running hna
需求:对applist.txt中的app进行修改,修改每个app前都需要进行确认。
4.2. 错误实现按照while read line
的思路,编写 main.sh 内容为:
1 2 3 4 5 6 7 8 #!/bin/bash while read line;do app_id=$(echo $line | awk '{print $1}' ) idc=$(echo $app_id | awk -F'.' '{print $NF}' ) bash modify.sh ${idc} ${app_id} done < applist.txt
modify.sh内容为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/bin/bash idc=$1 app_id=$2 echo -e "idc: ${idc} " echo -e "app_id: ${app_id} " read -p "确认进行修改?[Y/N]" inputif [[ $input = "y" || $input = "Y" ]];then echo -e "continue..." else echo -e "exit" exit 1 fi
但是问题来了,最终执行效果不符合预期,modify.sh中的确认交互效果会失效!这是因为read会读取缓存中的内容,而不是等待交互。
4.3. 更正实现那么,怎么解决上面的这个问题?非要使用while read line
的话,确实没有好的解决办法。 但是,我们可以把while read line
替换掉!新的 main.sh 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/bash filename="applist.txt" lines=$(cat ${filename} | sed '/^$/d' ) linenum=$(echo "${lines} " | wc -l) index=1 while [[ ${index} -le ${linenum} ]];do line=$(echo "${lines} " | sed -n "${index} p" ) app_id=$(echo $line | awk '{print $1}' ) idc=$(echo $app_id | awk -F'.' '{print $NF}' ) bash modify.sh ${idc} ${app_id} index=$((${index} +1 )) done
或者使用for循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/bin/bash filename="applist.txt" lines=$(cat ${filename} | sed '/^$/d' ) linenum=$(echo "${lines} " | wc -l) for index in `seq 1 ${linenum} `;do line=$(echo "${lines} " | sed -n "${index} p" ) app_id=$(echo $line | awk '{print $1}' ) idc=$(echo $app_id | awk -F'.' '{print $NF}' ) bash modify.sh ${idc} ${app_id} done
5. 读取文件并分批5.1. 需求描述需求:文件中有N行文本,读取文件内容后,平均分成M份,每份都组合成一个新的string,以逗号分隔。
文件文本示例内容为:
5.2. 脚本实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #!/bin/bash if [ "$# " -ne 1 ]; then echo "Usage: $0 <filename>" exit 1 fi filename=$1 M=3 if [ ! -f "$filename " ]; then echo "File not found!" exit 1 fi total_lines=$(wc -l < "$filename " ) lines_per_part=$(( ( total_lines + M - 1 ) / M )) for (( part=1 ; part<=M; part++ )); do start_line=$(( (part - 1 ) * lines_per_part + 1 )) end_line=$(( part * lines_per_part )) if [ $end_line -gt $total_lines ]; then end_line=$total_lines fi part_content=$(sed -n "${start_line} ,${end_line} p" "$filename " | tr '\n' ',' | sed 's/,$//' ) echo "part$part $part_content " if [ $end_line -eq $total_lines ]; then break fi done