Linux 下Python 腳本編寫的"奇技淫巧"

      網友投稿 1088 2025-04-02

      寫在前面


      對于自動化運維來講Python是一個利器

      常用的自動化運維工具Ansible就是通過python編寫

      博文為《Python Cookbook》讀書筆記整理而來

      涉及的內容都是編寫python運維腳本常用的一些知識點及Demo

      理解不足小伙伴幫忙指正

      寫在前面

      對于自動化運維來講Python是一個利器

      常用的自動化運維工具Ansible就是通過python編寫

      博文為《Python Cookbook》讀書筆記整理而來

      涉及的內容都是編寫python運維腳本常用的一些知識點及Demo

      理解不足小伙伴幫忙指正

      「 生命完美的答案,無非走過沒有遺憾 ---《天藍》」

      腳本編程與系統管理

      解析命令行選項

      「如何能夠解析腳本運行命令行選項(位于 sys.argv 中)」

      argparse 模塊可被用來解析命令行選項

      常用來定義一個腳本的說明文檔,一般我們寫python腳本會通過if..else 的方式來提供一個腳本說明文檔,python不支持switch。所有很麻煩,其實,我們可以通過argparse來編寫說明文檔。

      我們來看看執行一個python腳本

      對于熟悉linux的小伙伴下面的文檔在熟悉不過了,這個一個標準Linxu軟件包的說明文檔,文檔中定義是軟件包的說明

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./demo.py -h usage: demo.py [-h] -p pattern [-v] [-o OUTFILE] [--speed {slow,fast}] [filename [filename ...]] Search some files positional arguments: filename optional arguments: -h, --help show this help message and exit -p pattern, --pat pattern text pattern to search for -v verbose mode -o OUTFILE output file --speed {slow,fast} search speed ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      來看看這個腳本是如何編寫的

      #!/usr/bin/env python3 import argparse # 腳本的描述 parser = argparse.ArgumentParser(description='Search some files') # 腳本接收的全部參數,用`filenames`接收 parser.add_argument(dest='filenames', metavar='filename', nargs='*') # 腳本接收 parser.add_argument('-p', '--pat', metavar='pattern', required=True, dest='patterns', action='append', help='text pattern to search for') parser.add_argument('-v', dest='verbose', action='store_true', help='verbose mode') parser.add_argument('-o', dest='outfile', action='store', help='output file') parser.add_argument('--speed', dest='speed', action='store', choices={'slow', 'fast'}, default='slow', help='search speed') args = parser.parse_args() # Output the collected arguments print(args.filenames) print(args.patterns) print(args.verbose) print(args.outfile) print(args.speed)

      為了解析命令行選項, 首先要創建一個ArgumentParser實例, 并使用add_argument() 方法聲明你想要支持的選項。在每個add-argument()調用中:

      dest參數指定解析結果被指派給屬性的名字。 metavar 參數被用來生成幫助信息。

      action 參數指定跟屬性對應的處理邏輯,通常的值為 store , 被用來存儲某個值或將多個參數值收集到一個列表中。

      nargs 參數收集所有剩余的命令行參數到一個列表中。在本例中它被用來構造一個文件名列表

      parser.add_argument(dest='filenames',metavar='filename', nargs='*')

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py -p spam --pat=eggs foo.txt bar.txt ['foo.txt', 'bar.txt'] ['spam', 'eggs'] False None slow ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      action='store_true' 根據參數是否存在來設置一個 Boolean 標志:

      parser.add_argument('-v', dest='verbose', action='store_true', help='verbose mode')

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py -v -p spam --pat=eggs foo.txt bar.txt ['foo.txt', 'bar.txt'] ['spam', 'eggs'] True None slow

      action='store' 參數接受一個單獨值并將其存儲為一個字符串

      parser.add_argument('-o', dest='outfile', action='store', help='output file')

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py -v -p spam --pat=eggs -o liruilong foo.txt bar.txt ['foo.txt', 'bar.txt'] ['spam', 'eggs'] True liruilong slow

      action='append' 參數說明允許某個參數重復出現多次,并將它們追加到一個列表中去。

      required 標志表示該參數至少要有一個。-p 和 --pat 表示兩個參數名形式都可使用。

      parser.add_argument('-p', '--pat', metavar='pattern', required=True, dest='patterns', action='append', help='text pattern to search for')

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py -p spam foo.txt bar.txt ['foo.txt', 'bar.txt'] ['spam'] False None slow ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py --pat=eggs foo.txt bar.txt ['foo.txt', 'bar.txt'] ['eggs'] False None slow ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py -p spam --pat=eggs foo.txt bar.txt ['foo.txt', 'bar.txt'] ['spam', 'eggs'] False None slow

      如果一個都沒有,會提示缺少參數 -p/--pat

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py foo.txt bar.txt usage: demo.py [-h] -p pattern [-v] [-o OUTFILE] [--speed {fast,slow}] [filename [filename ...]] demo.py: error: the following arguments are required: -p/--pat ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      choices={'slow', 'fast'}, 參數說明接受一個值,但是會將其和可能的選擇值做比較,以檢測其合法性:

      parser.add_argument('--speed', dest='speed', action='store', choices={'slow', 'fast'}, default='slow', help='search speed')

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py --pat=eggs --speed 123 foo.txt bar.txt usage: demo.py [-h] -p pattern [-v] [-o OUTFILE] [--speed {slow,fast}] [filename [filename ...]] demo.py: error: argument --speed: invalid choice: '123' (choose from 'slow', 'fast') ┌──[root@liruilongs.github.io]-[~/python_demo] └─$python3 demo.py --pat=eggs --speed fast foo.txt bar.txt ['foo.txt', 'bar.txt'] ['eggs'] False None fast ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      一旦參數選項被指定,你就可以執行parser.parse()方法了。它會處理sys.argv的值并返回一個結果實例。每個參數值會被設置成該實例中add_argument()方法的 dest 參數指定的屬性值。

      還很多種其他方法解析命令行選項??梢詴謩拥奶幚?sys.argv 或者使用 getopt 模塊。但是,如果你采用本節的方式,將會減少很多冗余代碼,底層細節argparse 模塊已經幫你處理了。你可能還會碰到使用optparse庫解析選項的代碼。盡管 optparse 和 argparse 很像,但是后者更先進,因此在新的程序中你應該使用它。

      運行時彈出密碼輸入提示

      「你寫了個腳本,運行時需要一個密碼。此腳本是交互式的,因此不能將密碼在腳本中硬編碼,而是需要彈出一個密碼輸入提示,讓用戶自己輸入?!?/p>

      Python 的 getpass 模塊正是你所需要的。你可以讓你很輕松的彈出密碼輸入提示,并且不會在用戶終端回顯密碼。

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import getpass def svc_login(user, passwd): return user == passwd user = getpass.getuser() passwd = getpass.getpass() if svc_login(user, passwd): print('Yay!') else: print('Boo!')

      代碼中getpass.getuser()不會彈出用戶名的輸入提示。它會根據該用戶的 shell 環境或者會依據本地系統的密碼庫(支持 pwd 模塊的平臺)來使用當前用戶的登錄名

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./pass.py Password: #root Yay!

      通過重定向/管道/文件接受輸入

      在bash中編寫pytohn腳本接收外部數據的方式,一般情況下,對于一般變量,我們用命令行變量的方式比較多(手動的處理 sys.argv ),對于文件內容或者bash命令輸出直接通過腳本內部獲取需要的數據。

      其實python 腳本也可以用其他方式來接收 傳遞給他的文件數據或者bash命令輸出,包括將命令行的輸出通過管道傳遞給該腳本、重定向文件到該腳本,或在命令行中傳遞一個文件名或文件名列表給該腳本。

      這里通過 Python 內置的 fileinput 模塊,可以實現重定向,管道,文佳輸出的方式傳遞數據到腳本內部

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- """ @File : filein.py @Time : 2022/05/01 06:05:43 @Author : Li Ruilong @Version : 1.0 @Contact : 1224965096@qq.com @Desc : None """ # here put the import lib import fileinput with fileinput.input() as f_input: for line in f_input: print("腳本輸出", line, end='')

      使用fileinput.input()方法可以獲取當前輸入腳本的數據,腳本里面用一個FileInput迭代器接收

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$vim filein.py ┌──[root@liruilongs.github.io]-[~/python_demo] └─$chmod +x filein.py

      文件直接接收

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./filein.py /etc/passwd 腳本輸出 root:x:0:0:root:/root:/bin/bash 腳本輸出 bin:x:1:1:bin:/bin:/sbin/nologin 腳本輸出 daemon:x:2:2:daemon:/sbin:/sbin/nol 。。。。

      重定向接收

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./filein.py < /etc/passwd 腳本輸出 root:x:0:0:root:/root:/bin/bash 腳本輸出 bin:x:1:1:bin:/bin:/sbin/nologin 。。。。。。

      管道方式接收

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$df -h 文件系統 容量 已用 可用 已用% 掛載點 /dev/sda1 150G 22G 129G 15% / devtmpfs 983M 0 983M 0% /dev tmpfs 993M 0 993M 0% /dev/shm tmpfs 993M 17M 976M 2% /run tmpfs 993M 0 993M 0% /sys/fs/cgroup overlay 150G 22G 129G 15% /var/lib/docker/overlay2/9fbd33d3485f02eadef6907a5b4eaead4a384684b66c572d822a2942a82ca0d5/merged overlay 150G 22G 129G 15% /var/lib/docker/overlay2/85ff22ccaf2db68a0a863bc404d79d72fa6c8744424f50ba8fb6bfa83d56b56a/merged tmpfs 199M 0 199M 0% /run/user/0 ┌──[root@liruilongs.github.io]-[~/python_demo] └─$df -h | ./filein.py 腳本輸出 文件系統 容量 已用 可用 已用% 掛載點 腳本輸出 /dev/sda1 150G 22G 129G 15% / 腳本輸出 devtmpfs 983M 0 983M 0% /dev 腳本輸出 tmpfs 993M 0 993M 0% /dev/shm 腳本輸出 tmpfs 993M 17M 976M 2% /run 腳本輸出 tmpfs 993M 0 993M 0% /sys/fs/cgroup 腳本輸出 overlay 150G 22G 129G 15% /var/lib/docker/overlay2/9fbd33d3485f02eadef6907a5b4eaead4a384684b66c572d822a2942a82ca0d5/merged 腳本輸出 overlay 150G 22G 129G 15% /var/lib/docker/overlay2/85ff22ccaf2db68a0a863bc404d79d72fa6c8744424f50ba8fb6bfa83d56b56a/merged 腳本輸出 tmpfs 199M 0 199M 0% /run/user/0 ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      fileinput.input() 創建并返回一個FileInput類的實例,該實例可以被當做一個上下文管理器使用。因此,整合起來,如果我們要寫一個打印多個文件輸出的腳本,那么我們需要在輸出中包含文件名和行號

      >>> import fileinput >>> with fileinput.input("/etc/passwd") as f: ... for line in f: ... print(f.filename(),f.fileno(),f.lineno(),line,end='') ... /etc/passwd 3 1 root:x:0:0:root:/root:/bin/bash /etc/passwd 3 2 bin:x:1:1:bin:/bin:/sbin/nologin /etc/passwd 3 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin /etc/passwd 3 4 adm:x:3:4:adm:/var/adm:/sbin/nologin /etc/passwd 3 5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin /etc/passwd 3 6 sync:x:5:0:sync:/sbin:/bin/sync

      執行外部命令并獲取它的輸出

      「你想執行一個外部命令并以 Python 字符串的形式獲取執行結果。」

      使用subprocess.check_output()函數。

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import subprocess out_bytes = subprocess.check_output(['netstat','-a']) out_text = out_bytes.decode('utf-8') print(out_text)

      執行下試試

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./py_sh.py Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 localhost:2379 0.0.0.0:* LISTEN tcp 0 0 vms55.rhce.cc:2379 0.0.0.0:* LISTEN tcp 0 0 localhost:2380 0.0.0.0:* LISTEN tcp 0 0 vms55.rhce.cc:2380 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:webcache 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN

      如果被執行的命令以非零碼返回,就會拋出異常。下面的例子捕獲到錯誤并獲取返回碼:

      try: out_bytes = subprocess.check_output(['cmd','arg1','arg2']) except subprocess.CalledProcessError as e: out_bytes = e.output # Output generated before error code = e.returncode # Return code

      默認情況下,check_output() 僅僅返回輸入到標準輸出的值。如果你需要同時收集標準輸出和錯誤輸出,使用stderr參數:

      out_bytes = subprocess.check_output(['cmd','arg1','arg2'],stderr=subprocess.STDOUT)

      如果你需要用一個超時機制來執行命令,使用 timeout 參數:

      try: out_bytes = subprocess.check_output(['cmd','arg1','arg2'], timeout=5) except subprocess.TimeoutExpired as e: ....

      通常來講,命令的執行不需要使用到底層 shell 環境(比如 sh、bash)。一個字符串列表會被傳遞給一個低級系統命令,比如 os.execve() 。

      如果你想讓命令被一個shell 執行,傳遞一個字符串參數,并設置參數 shell=True . 有時候你想要Python去執行一個復雜的 shell 命令的時候這個就很有用了,比如管道流、I/O 重定向和其他特性。例如:

      out_bytes = subprocess.check_output('grep python | wc > out', shell=True)

      是在 shell 中執行命令會存在一定的安全風險,特別是當參數來自于用戶輸入時。這時候可以使用 shlex.quote() 函數來將參數正確的用雙引用引起來。

      使用 check_output() 函數是執行外部命令并獲取其返回值的最簡單方式。但是,如果你需要對子進程做更復雜的交互,比如給它發送輸入,你得采用另外一種方法。這時候可直接使用subprocess.Popen類。

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import subprocess # Some text to send text = b''' hello world this is a test goodbye ''' # Launch a command with pipes p = subprocess.Popen(['wc'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) # Send the data and get the output stdout, stderr = p.communicate(text) # To interpret as text, decode out = stdout.decode('utf-8') err = stderr.decode('utf-8')

      關于子進程,簡單來看下

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$(pwd;echo $BASH_SUBSHELL;ps --forest) /root/python_demo 1 PID TTY TIME CMD 9324 pts/0 00:00:00 bash 49906 pts/0 00:00:00 \_ bash 49907 pts/0 00:00:00 \_ ps ┌──[root@liruilongs.github.io]-[~/python_demo] └─$pwd;echo $BASH_SUBSHELL;ps --forest /root/python_demo 0 PID TTY TIME CMD 9324 pts/0 00:00:00 bash 49908 pts/0 00:00:00 \_ ps ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      也可以進程列表同協程結合的方式。你既可以在子shell中 進行繁重的處理工作,同時也不會讓子shell的I/O受制于終端。

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$coproc (sleep 10;ps --forest;sleep 10;ps --forest) [1] 50326 ┌──[root@liruilongs.github.io]-[~/python_demo] └─$jobs [1]+ 運行中 coproc COPROC ( sleep 10; ps --forest; sleep 10; ps --forest ) &

      如果直接丟到后臺會自動在終端輸出IO

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$( sleep 10; ps --forest; sleep 10; ps --forest ) & [1] 50335 ┌──[root@liruilongs.github.io]-[~/python_demo] └─$ps --forest PID TTY TIME CMD 9324 pts/0 00:00:00 bash 50335 pts/0 00:00:00 \_ bash 50336 pts/0 00:00:00 | \_ sleep 50337 pts/0 00:00:00 \_ ps ┌──[root@liruilongs.github.io]-[~/python_demo] └─$ PID TTY TIME CMD 9324 pts/0 00:00:00 bash 50335 pts/0 00:00:00 \_ bash 50340 pts/0 00:00:00 \_ ps [1]+ 完成 ( sleep 10; ps --forest; sleep 10; ps --forest ) ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      subprocess 模塊對于依賴 TTY 的外部命令不合適用。例如,你不能使用它來自動化一個用戶輸入密碼的任務(比如一個 ssh 會話)。這時候,你需要使用到第三方模塊了,比如基于著名的 expect 家族的工具(pexpect 或類似的)(pexpect可以理解為linux下的expect的Python封裝、通過pexpect可以實現對ssh、ftp、passwd、telnet等命令行進行自動交互,而無需人工干涉來達到自動化的目的。比如我們可以模擬一個FTP登錄時所有交互,包括輸入主機地址、用戶名、密碼、上傳文件等,待出現異常還可以進行嘗試自動處理。)

      終止程序并給出錯誤信息

      「你想向標準錯誤打印一條消息并返回某個非零狀態碼來終止程序運行」

      通過 python的raise SystemExit(3)命令可以主動拋出一個錯誤,通過sys.stderr.write將命令寫到標準的輸出端

      #!/usr/bin/env python3 import sys sys.stderr.write('It failed!\n') raise SystemExit(3)

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$vim err.py ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./err.py It failed! ┌──[root@liruilongs.github.io]-[~/python_demo] └─$echo $? 3

      直接將消息作為參數傳給SystemExit(),那么你可以省略其他步驟

      #!/usr/bin/env python3 raise SystemExit('It failed!')

      拋出一個 SystemExit 異常,使用錯誤消息作為參數,它會將消息在sys.stderr中打印,然后程序以狀態碼1退出

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./err.py It failed! ┌──[root@liruilongs.github.io]-[~/python_demo] └─$echo $? 1

      獲取終端的大小

      「你需要知道當前終端的大小以便正確的格式化輸出?!?/p>

      使用 os.get terminal size() 函數來做到這一點。

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import os sz = os.get_terminal_size() print(sz) print(sz.columns) print(sz.lines)

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./tim.py os.terminal_size(columns=99, lines=30) 99 30 ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./tim.py os.terminal_size(columns=165, lines=30) 165 30 ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      復制或者移動文件和目錄

      「復制或移動文件和目錄,但是又不想調用 shell 命令。」

      shutil 模塊有很多便捷的函數可以復制文件和目錄。使用起來非常簡單

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import shutil # Copy src to dst. (cp src dst) shutil.copy(src, dst) # Copy files, but preserve metadata (cp -p src dst) shutil.copy2(src, dst) # Copy directory tree (cp -R src dst) shutil.copytree(src, dst) # Move src to dst (mv src dst) shutil.move(src, dst)

      這里不多講,熟悉Linux的小伙伴應該不陌生。

      默認情況下,對于符號鏈接這些命令處理的是它指向的東西文件。例如,如果源文件是一個符號鏈接,那么目標文件將會是符號鏈接指向的文件。如果你只想復制符號鏈接本身,那么需要指定關鍵字參數 follow_symlinks

      shutil.copytree(src, dst, symlinks=True)

      Linux 下Python 腳本編寫的"奇技淫巧"

      copytree() 可以讓你在復制過程中選擇性的忽略某些文件或目錄。你可以提供一個忽略函數,接受一個目錄名和文件名列表作為輸入,返回一個忽略的名稱列表。例如:

      def ignore_pyc_files(dirname, filenames): return [name in filenames if name.endswith('.pyc')] shutil.copytree(src, dst, ignore=ignore_pyc_files)

      對于文件元數據信息,copy2() 這樣的函數只能盡自己最大能力來保留它。訪問時間、創建時間和權限這些基本信息會被保留,但是對于所有者、ACLs、資源 fork 和其他更深層次的文件元信息就說不準了

      通常不會去使用 shutil.copytree() 函數來執行系統備份。當處理文件名的時候,最好使用os.path中的函數來確保最大的可移植性

      >>> filename = '/etc/docker/daemon.json' >>> import os.path >>> os.path.basename(filename) 'daemon.json' >>> os.path.dirname(filename) '/etc/docker' >>> os.path.split(filename) ('/etc/docker', 'daemon.json') >>> os.path.join('/new/dir', os.path.basename(filename)) '/new/dir/daemon.json' >>> os.path.expanduser('~/guido/programs/daemon.json') '/root/guido/programs/daemon.json' >>>

      使用copytree()復制文件夾的一個棘手的問題是對于錯誤的處理,可以使用異常塊處理,或者通過 參數 ignore dangling symlinks=True忽略掉無效符號鏈接。

      try: shutil.copytree(src, dst) except shutil.Error as e: for src, dst, msg in e.args[0]: # src is source name # dst is destination name # msg is error message from exception print(dst, src, msg)

      創建和解壓歸檔文件

      「創建或解壓常見格式的歸檔文件(比如.tar, .tgz 或.zip)」

      shutil 模塊擁有兩個函數—— make archive() 和 unpack archive() 可派上用場,

      >>> import shutil >>> shutil.unpack_archive('Python-3.3.0.tgz') >>> shutil.make_archive('py33','zip','Python-3.3.0') '/Users/beazley/Downloads/py33.zip'

      make archive() 的第二個參數是期望的輸出格式。可以使用get archive formats()獲取所有支持的歸檔格式列表。

      >>> import shutil >>> shutil.get_archive_formats() [('bztar', "bzip2'ed tar-file"), ('gztar', "gzip'ed tar-file"), ('tar', 'uncompressed tar file'), ('xztar', "xz'ed tar-file"), ('zip', 'ZIP file')] >>>

      通過文件名查找文件

      「你需要寫一個涉及到文件查找操作的腳本,比如對日志歸檔文件的重命名工具,你不想在 Python 腳本中調用 shell,或者你要實現一些 shell 不能做的功能。」

      查找文件,可使用 os.walk() 函數,傳一個頂級目錄名給它

      #!/usr/bin/env python3 import os,sys def findfile(start, name): for relpath, dirs, files in os.walk(start): if name in files: full_path = os.path.join(start, relpath, name) print(os.path.normpath(os.path.abspath(full_path))) if __name__ == '__main__': findfile(sys.argv[1], sys.argv[2])

      os.walk() 方法為我們遍歷目錄樹,每次進入一個目錄,它會返回一個三元組,包含相對于查找目錄的相對路徑,一個該目錄下的目錄名列表,以及那個目錄下面的文件名列表。

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./find.py /etc/ passwd /etc/passwd /etc/pam.d/passwd ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      對于每個元組,只需檢測一下目標文件名是否在文件列表中。如果是就使用os.path.join() 合并路徑。為了避免奇怪的路徑名比如 ././foo//bar ,使用了另外兩個函數來修正結果

      第一個是os.path.abspath(), 它接受一個路徑,可能是相對路徑,最后返回絕對路徑。

      第二個是os.path.normpath(),用來返回正常路徑,可以解決雙斜桿、對目錄的多重引用的問題等。

      os.walk(start)還有跨平臺的優勢。并且,還能很輕松的加入其他的功能。我們再演示一個例子,下面的函數打印所有最近被修改過的文件:

      #!/usr/bin/env python3 import os import time import sys def modified_within(top, seconds): now = time.time() for path, dirs, files in os.walk(top): for name in files: fullpath = os.path.join(path, name) if os.path.exists(fullpath): mtime = os.path.getmtime(fullpath) if mtime > (now - seconds): print(fullpath) if __name__ == '__main__': if len(sys.argv) != 3: print('Usage: {} dir seconds'.format(sys.argv[0])) raise SystemExit(1) modified_within(sys.argv[1], float(sys.argv[2]))

      打印10分鐘之前被修改的數據

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./find.py /etc/ 10 /etc/mtab ┌──[root@liruilongs.github.io]-[~/python_demo] └─$ll /etc/mtab lrwxrwxrwx. 1 root root 17 10月 18 2018 /etc/mtab -> /proc/self/mounts ┌──[root@liruilongs.github.io]-[~/python_demo] └─$ll /proc/self/mounts -r--r--r-- 1 root root 0 5月 2 01:18 /proc/self/mounts ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      讀取配置文件

      「怎樣讀取普通.ini 格式的配置文件?」

      configparser 模塊能被用來讀取配置文件

      編寫配置文件

      ; config.ini ; Sample configuration file [installation] library=%(prefix)s/lib include=%(prefix)s/include bin=%(prefix)s/bin prefix=/usr/local # Setting related to debug configuration [debug] log_errors=true show_warnings=False [server] port: 8080 nworkers: 32 pid-file=/tmp/spam.pid root=/www/root signature: ================================= Brought to you by the Python Cookbook =================================

      >>> from configparser import ConfigParser >>> cfg = ConfigParser() >>> cfg.read('config.ini') ['config.ini'] >>> cfg.sections() ['installation', 'debug', 'server'] >>> cfg.get('installation','library') '/usr/local/lib' >>> cfg.getboolean('debug','log_errors') True >>> cfg.getint('server','port') 8080 >>> cfg.getint('server','nworkers') 32 >>> print(cfg.get('server','signature')) ================================= Brought to you by the Python Cookbook ================================= >>>

      如果有需要,你還能修改配置并使用cfg.write()方法將其寫回到文件中

      >>> from configparser import ConfigParser >>> cfg = ConfigParser() >>> cfg.read('config.ini') ['config.ini'] >>> cfg.set('server','port','9000') >>> cfg.set('debug','log_errors','False') >>> import sys >>> cfg.write(sys.stdout) [installation] library = %(prefix)s/lib include = %(prefix)s/include bin = %(prefix)s/bin prefix = /usr/local [debug] log_errors = False show_warnings = False [server] port = 9000 nworkers = 32 pid-file = /tmp/spam.pid root = /www/root signature = ================================= Brought to you by the Python Cookbook ================================= >>>

      配置文件中的名字是不區分大小寫

      解析值的時候,getboolean() 方法查找任何可行的值。

      ConfigParser 能一次讀取多個配置文件然后合并成一個配置。后面讀取的配置文件會覆蓋前面的配置文件

      給簡單腳本增加日志功能

      「你希望在腳本和程序中將診斷信息寫入日志文件?!?/p>

      python 腳本打印日志最簡單方式是使用 logging 模塊

      #`!/usr/bin/env python3 # -*- encoding: utf-8 -*- import logging def main(): # Configure the logging system logging.basicConfig(filename='app.log', level=logging.ERROR) # Variables (to make the calls that follow work) hostname = 'www.python.org' item = 'spam' filename = 'data.csv' mode = 'r' # Example logging calls (insert into your program) logging.critical('Host %s unknown', hostname) logging.error("Couldn't find %r", item) logging.warning('Feature is deprecated') logging.info('Opening file %r, mode=%r', filename, mode) logging.debug('Got here') if __name__ == '__main__': main()

      五個日志調用(critical(), error(), warning(), info(), debug())以降序方式表示不同的嚴重級別。 basicConfig() 的level參數是一個過濾器。所有級別低于此級別的日志消息都會被忽略掉。每個logging操作的參數是一個消息字符串,后面再跟一個或多個參數。構造最終的日志消息的時候我們使用了% 操作符來格式化消息字符串。

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./logger.py ┌──[root@liruilongs.github.io]-[~/python_demo] └─$cat app.log CRITICAL:root:Host www.python.org unknown ERROR:root:Couldn't find 'spam' ┌──[root@liruilongs.github.io]-[~/python_demo] └─$

      如果你想使用配置文件,可以像下面這樣修改basicConfig()調用:

      import logging import logging.config def main(): # Configure the logging system logging.config.fileConfig('logconfig.ini')

      logconfig.ini

      [loggers] keys=root [handlers] keys=defaultHandler [formatters] keys=defaultFormatter [logger_root] level=INFO handlers=defaultHandler qualname=root [handler_defaultHandler] class=FileHandler formatter=defaultFormatter args=('app.log', 'a') [formatter_defaultFormatter] format=%(levelname)s:%(name)s:%(message)s

      在調用日志操作前先執行下 basicConfig() 函數方法,可以找標準輸出或者文件中輸出

      basicConfig() 在程序中只能被執行一次。如果你稍后想改變日志配置,就需要先獲取 root logger ,然后直接修改它。

      logging.getLogger().level = logging.DEBUG

      更多見日志模塊文檔https://docs.python.org/3/howto/logging-cookbook.html

      給函數庫增加日志功能

      「你想給某個函數庫增加日志功能,但是又不能影響到那些不使用日志功能的程序?!?/p>

      對于想要執行日志操作的函數庫,你應該創建一個專屬的logger對象,并且像下面這樣初始化配置:

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import logging log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) # Example function (for testing) def func(): log.critical('A Critical Error!') log.debug('A debug message') func()

      使用這個配置,默認情況下不會打印日志,只有配置過日志系統,那么日志消息打印就開始生效

      logging.basicConfig()

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./logg.py CRITICAL:__main__:A Critical Error!

      通常來講,不應該在函數庫代碼中自己配置日志系統,或者是已經有個已經存在的日志配置了。調用getLogger( name )創建一個和調用模塊同名的 logger 模塊。由于模塊都是唯一的,因此創建的 logger 也將是唯一的。所以當前進程中只有一個logging會生效。

      log.addHandler(logging.NullHandler()) 操作將一個空處理器綁定到剛剛已經創建好的 logger 對象上。一個空處理器默認會忽略調用所有的日志消息。因此,如果使用該函數庫的時候還沒有配置日志,那么將不會有消息或警告出現。

      在這里,根日志被配置成僅僅輸出 ERROR 或更高級別的消息。不過,somelib 的日志級別被單獨配置成可以輸出 debug 級別的消息,它的優先級比全局配置高。像這樣更改單獨模塊的日志配置對于調試來講是很方便的,因為你無需去更改任何的全局日志配置——只需要修改你想要更多輸出的模塊的日志等級。(這個還有待研究)

      實現一個計時器

      「你想記錄程序執行多個任務所花費的時間」

      time 模塊包含很多函數來執行跟時間有關的函數。盡管如此,通常我們會在此基礎之上構造一個更高級的接口來模擬一個計時器。

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import time class Timer: def __init__(self, func=time.perf_counter): self.elapsed = 0.0 self._func = func self._start = None def start(self): if self._start is not None: raise RuntimeError('Already started') self._start = self._func() def stop(self): if self._start is None: raise RuntimeError('Not started') end = self._func() self.elapsed += end - self._start self._start = None def reset(self): self.elapsed = 0.0 @property #類的屬性私有化,那么可以使用@property 使屬性可以被外部訪問并修改 def running(self): return self._start is not None def __enter__(self): self.start() return self def __exit__(self, *args): self.stop()

      這個類定義了一個可以被用戶根據需要啟動、停止和重置的計時器。它會在elapsed 屬性中記錄整個消耗時間。下面是一個例子來演示怎樣使用它:

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import time class Timer: def __init__(self, func=time.perf_counter): self.elapsed = 0.0 self._func = func self._start = None def start(self): if self._start is not None: raise RuntimeError('Already started') self._start = self._func() def stop(self): if self._start is None: raise RuntimeError('Not started') end = self._func() self.elapsed += end - self._start self._start = None def reset(self): self.elapsed = 0.0 @property #類的屬性私有化,那么可以使用@property 使屬性可以被外部訪問并修改 def running(self): return self._start is not None def __enter__(self): self.start() return self def __exit__(self, *args): self.stop() def countdown(n): while n > 0: n -= 1 # Use 1: Explicit start/stop t = Timer() t.start() countdown(1000000) t.stop() print(t.elapsed) # Use 2: As a context manager with t: countdown(1000000) print(t.elapsed) with Timer() as t2: countdown(1000000) print(t2.elapsed)

      這里通過__enter__,__exit__ ,使用with 語句以及上下文管理器協議可以省略計時器打開和關閉操作。(關于上下文管理協議,即with語句,為了讓一個對象兼容with語句,必須在這個對象的類中聲明__enter__和__exit__方法,,__enter__在出現with語句被調用,__exit__在代碼執行完畢被調用,可以參考open()方法)

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./times.py 0.05191648800246185 0.12038616700374405 0.06592946800083155

      在計時中要考慮一個底層的時間函數問題。 一般來說, 使用 time.time()或time.clock()計算的時間精度因操作系統的不同會有所不同。而使用time.perf_counter() 函數可以確保使用系統上面最精確的計時器。

      限制腳本的內存和CPU的使用量

      「你想對在 Unix 系統上面運行的程序設置內存或 CPU 的使用限制?!?/p>

      resource 模塊能同時執行這兩個任務。例如,要限制 CPU 時間,下面的代碼在windows平臺執行不了,但是Linux是可以的。

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import signal import resource import os def time_exceeded(signo, frame): print("Time's up!") raise SystemExit(1) def set_max_runtime(seconds): # 安裝信號處理程序并設置資源限制 soft, hard = resource.getrlimit(resource.RLIMIT_CPU) # 限制CUP使用時間為15秒 resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard)) # 進程即將結束,給一個信號量,加一個回調 signal.signal(signal.SIGXCPU, time_exceeded) if __name__ == '__main__': set_max_runtime(15) while True: pass

      程序運行時,SIGXCPU 信號在時間過期時被生成,然后執行清理并退出。

      ┌──[root@liruilongs.github.io]-[~/python_demo] └─$vim cpu.py ┌──[root@liruilongs.github.io]-[~/python_demo] └─$chmod +x cpu.py ┌──[root@liruilongs.github.io]-[~/python_demo] └─$./cpu.py Time's up!

      這暫時沒有好的Demo...

      #!/usr/bin/env python3 # -*- encoding: utf-8 -*- import resource def limit_memory(maxsize): soft, hard = resource.getrlimit(resource.RLIMIT_AS) resource.setrlimit(resource.RLIMIT_AS, (maxsize, hard)) # 0.5 * 1024 ^ 6 = 576460752303423488 設置最大內存500M limit_memory(576460752303423488)

      程序運行到沒有多余內存時會拋出 MemoryError 異常。

      setrlimit() 函數被用來設置特定資源上面的軟限制和硬限制。

      軟限制是一個值,當超過這個值的時候操作系統通常會發送一個信號來限制或通知該進程.

      >>> resource.RLIMIT_AS 9

      硬限制是用來指定軟限制能設定的最大值。通常來講,這個由系統管理員通過設置系統級參數來決定。盡管硬限制可以改小一點,但是最好不要使用用戶進程去修改。

      >>> resource.getrlimit(resource.RLIMIT_AS) (-1, -1)

      setrlimit() 函數還能被用來設置子進程數量、打開文件數以及類似系統資源的限制(cgroup)。

      啟動一個WEB瀏覽器

      「通過腳本啟動瀏覽器并打開指定的 URL 網頁」

      webbrowser 模塊能被用來啟動一個瀏覽器,并且與平臺無關

      Windows PowerShell 版權所有 (C) Microsoft Corporation。保留所有權利。 嘗試新的跨平臺 PowerShell https://aka.ms/pscore6 PS E:\docker> python Python 3.9.0 (tags/v3.9.0:9cf6752, Oct 5 2020, 15:23:07) [MSC v.1927 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> >>> import webbrowser >>> webbrowser.open('http://www.python.org') True >>>

      新窗口打卡網站

      webbrowser.open_new('http://www.python.org')

      當前窗口打開一個tab頁

      webbrowser.open_new_tab('http://www.python.org')

      指定瀏覽器類型,可以使用 webbrowser.get() 函數

      >>> c = webbrowser.get('firefox') >>> c.open('http://www.python.org') True >>> c.open_new_tab('http://docs.python.org') True >>>

      Python

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:excel對比數據教程
      下一篇:excel表格美觀設計(好看的excel表格設計范例)
      相關文章
      国产天堂亚洲精品| 亚洲色偷偷色噜噜狠狠99 | 日韩亚洲国产综合高清| 国产亚洲精品资源在线26u| 亚洲成人在线电影| 亚洲日韩中文无码久久| 亚洲综合色区在线观看| 亚洲片国产一区一级在线观看| 亚洲Av永久无码精品黑人| 亚洲国产成人手机在线观看| 亚洲中文字幕无码爆乳| 涩涩色中文综合亚洲| 亚洲人成人无码.www石榴| 中文无码亚洲精品字幕| jiz zz在亚洲| 亚洲国产成人无码AV在线影院| 亚洲乱码日产精品一二三| 亚洲日韩国产二区无码| 亚洲国产欧美国产综合一区 | 无码专区一va亚洲v专区在线| 含羞草国产亚洲精品岁国产精品| 亚洲国产精品无码久久九九大片| 337p日本欧洲亚洲大胆人人| 亚洲AV一区二区三区四区| 亚洲av区一区二区三| 国产91精品一区二区麻豆亚洲| 狠狠亚洲婷婷综合色香五月排名| 在线精品亚洲一区二区小说| 国产成A人亚洲精V品无码 | 久久久久久亚洲av成人无码国产| 久久亚洲精品国产精品黑人| 亚洲午夜未满十八勿入| 亚洲激情黄色小说| 亚洲免费福利在线视频| 亚洲AV无码AV日韩AV网站| 亚洲国产成人久久一区WWW| 亚洲综合国产一区二区三区| 久久综合图区亚洲综合图区| 自怕偷自怕亚洲精品| 国产成人亚洲合集青青草原精品| 亚洲老熟女五十路老熟女bbw|