编写脚本读取文件并执行命令

最近遇到如下需求:

有一个文件,文件格式大概是这样

<filename1>\t<download_url1>

<filename2>\t<download_url2>

编写一个脚本循环读取该文件的每一行,使用axel命令将download_url下载下来,保存为本地文件filename

Bash

先给出Code:

#! /bin/bash

# ./download.sh <filename>

cat $1 | while read line
do
# echo ${line}
array=(${line// / }) # array=(${line//\t/ }) 不生效
# echo ${array[0]} + ";" + ${array[1]}
cmd='axel -o '${array[0]}' "'${array[1]}'"'
eval $cmd
done

下面对code逐一解释

#! /bin/bash

#!/bin/bash是指此脚本使用/bin/bash来解释执行,Bash脚本首行固定写法。

Bash注释

Bash中使用#进行单行注释,见如上代码第三行

Bash 接收外部参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$nn 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

特殊的 ,$0 表示脚本名称(含路径)。

另外,$# 表示传递参数的个数。

上面代码中cat $1 |就是将第一个参数作为文件名,读取文件中的内容,通过管道命令|将文件内容传给while循环

Bash 循环读取每一行

最简单的

while read line
do
echo $line
done

还可以使用for循环等,详细可参见参考资料1。

Bash按指定字符分割字符串

接上文,line是待处理的字符串,则指定分隔符\t将line分割后的字符串数组存放到array,

array=(${line// / })

注意这里///之间是一个制表符,而不是空格。

我也曾使用array=(${line//\t/ })来进行分割,但是测试后发现它会按照字符t对字符串进行分割。

还没弄明白语法规则

Bash字符串拼接

cmd='axel -o '${array[0]}' "'${array[1]}'"'

这一行就是取array中的值进行字符串拼接,注意取值的语法${}

  • Bash中对字符串拼接不需要使用+进行连接

  • 这里使用单引号是为了避免对双引号转义

Bash执行cmd

eval $cmd是读取变量cmd的值当作一条Shell命令进行执行

改进

上面的代码先读取每一行,在对行按照制表符进行分割。其实可以更简洁:

while read filename	url
do
#echo $filename
done

同样的需要注意filenameurl之间是一个制表符而不是空格。

问题

当url过长时,该脚本并不能完整的读取url。例如,当文件如下时

covid19-image-dataset-collection-volumes-folder.zip	https://storage.googleapis.com/kaggle-data-sets/862993/1470970/bundle/archive.zip?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcp-kaggle-com%40kaggle-161607.iam.gserviceaccount.com%2F20211221%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20211221T052609Z&X-Goog-Expires=259199&X-Goog-SignedHeaders=host&X-Goog-Signature=6549111df89ca0440058e7061ba9a13d72ea22ed7bcfdbcfc56ddadaf7a54ec4d3285487b269808d60cf0568264db1803899989475d8ca0aeec0777891f54df612a6a8f7ba808c8df3899feda1a94ca0507a580e407c0e42371d97e3ce1c75d598bed1f693bf841f7d4660294488e99fc944d6e1498c5ec38b431b969e5f13eda5f2d8029e855ea368c57eab60897a32f7556df116a96bfd321dd4d214dfe847d4c8ca11fdb7c3472ebac9f7a9eb20b44a148df51ecb2f93acdc5459800a894cdc2d62a0cc2c2e42e6711e568827f44b82d6faaa02aa1357c8d93140eb2c72bde9b8c1716e565800ac3ff812df304ce7a54c2a83896696ac5fa935329afa66ef

读取并拼接后的命令字符串如:

axel -o covid19-image-dataset-collection-volumes-folder.zip "https://storage.googleapis.com/kaggle-data-sets/862993/1470970/bundle/archive.zip?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcp-kaggle-com%40kaggle-161607.iam.gserviceaccount.com%2F20211221%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20211221T052609Z&X-Goog-Expires=259199&X-Goog-SignedHeaders=host&X-Goog-Signature=6549111df89ca0440058e7061ba9a13d72ea22ed7bcfdbcfc56ddadaf7a54ec4d3285487b269808d60cf0568264db1803899989475d8ca0aeec0777891f54df612a6a8f7ba808c8df3899feda1a94ca0507a580e407c0e42371d97e3ce1c75d598bed1f693bf841f7d4660294488e99fc944d6e1498c5ec38b431b969e5f13eda5f2d8029e855ea368c57eab60897a32f7556df116a96bfd321dd4d214dfe847d4c8ca11fdb7c3472ebac9f7a9eb20b44a148df51ecb2f93acdc5459800a894cdc2d62a0cc2c2e42e6711e568827f44b82d6faaa02aa1357c8d93140eb2c72bde9b8c1716e565800ac3ff812df304ce7a54c2a83896696ac5fa9353"9afa66ef

注意引号外面还有数据,这是不符合预期的。

目前还没有解决该问题

Python

由于本人对Bash不是很熟悉,所以改用Python来完成这一需求。

下面给出完整代码

import argparse
import os


#python download.py -f list

def main():
parser = argparse.ArgumentParser(description="download kaggle dataset according to file")
parser.add_argument('-f', '--filename', required=True, type=str, help='filename')

args = parser.parse_args()

filename = args.filename

with open(filename, "r") as f:
lines = f.readlines()

for line in lines:
arr = line.split('\t')
save_name = arr[0]
url = arr[1].replace("\n", "")
cmd = 'axel -o ' + save_name + ' "' + url + '"'
# print(cmd)
res_f = os.popen(cmd) # 返回的是一个文件对象
print(res_f.read())


if __name__ == '__main__':
main()

Python main函数

if __name__ == '__main__':
main()

这是Python的main函数,Python脚本总会从该“函数“进入。

Python 接收外部参数

使用类库argparse来读取外部参数。上述代码给出了一个简单的示例

parser = argparse.ArgumentParser(description="download kaggle dataset according to file")
'''
第一、二个参数是指定参数名,第一个是简写形式
在调用命令的时候,可以使用python script.py -f <param>或者python script.py --filename <param>
required=True 表示该参数必填
type=str 指定参数类型
help='filename' 指定提示语
'''
parser.add_argument('-f', '--filename', required=True, type=str, help='filename' 指定提示语)

args = parser.parse_args()

filename = args.filename # filename存放了外部参数的值

详细请参见参考资料2

Python文件、字符串操作

在Python中执行Shell命令

有两种方式。使用os.system("command")执行无返回值的Shell命令;使用f = os.popen("command")执行有输出的Shell命令,其返回值f是一个文件对象,通过f.read()来读取命令输出内容。


参考资料

  1. Shell 脚本学习指南
  2. Python Argparse 教程
  3. python执行shell脚本的几种方法
文章作者: Met Guo
文章链接: https://guoyujian.github.io/2021/12/21/%E7%BC%96%E5%86%99%E8%84%9A%E6%9C%AC%E8%AF%BB%E5%8F%96%E6%96%87%E4%BB%B6%E5%B9%B6%E6%89%A7%E8%A1%8C%E5%91%BD%E4%BB%A4/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Gmet's Blog