#-*-Coding: UTF-8 -*- Import OS Import subprocess Import signal Import pwd Import sys Class MockLogger (object ): ''' Simulate the log class. Facilitate unit testing. ''' Def _ init _ (self ): Self.info = self. error = self. critical = self. debug Def debug (self, msg ): Print "LOGGER:" + msg Class Shell (object ): '''To wrap the Shell script. The execution results are stored in Shell. ret_code, Shell. ret_info, and Shell. err_info. Run () is a normal call and will wait for the shell command to return. Run_background () is an asynchronous call and will return immediately without waiting for the completion of the shell command During asynchronous calling, you can use get_status () to query the status, or use wait () to enter the blocking status, Wait until the shell execution is complete. After you use kill () to forcibly stop the script during asynchronous calling, you still need to use wait () to wait for the script to exit. TODO does not verify that Shell commands contain ultra-large output results. ''' Def _ init _ (self, cmd ): Self. cmd = cmd # cmd includes commands and Parameters Self. ret_code = None Self. ret_info = None Self. err_info = None # You can replace it with a specific logger. Self. logger = MockLogger () Def run_background (self ): ''' Execute the shell command in non-blocking mode (default mode of Popen ). ''' Self. logger. debug ("run % s" % self. cmd) # Popen will throw an OSError exception when the command to be executed does not exist, but after shell = True, # Shell will handle errors that do not exist in the command, so there is no OSError exception, so you do not need to handle it Self. _ process = subprocess. Popen (self. cmd, shell = True, Stdout = subprocess. PIPE, stderr = subprocess. PIPE) # non-blocking Def run (self ): ''' Execute shell commands in blocking mode. ''' Self. run_background () Self. wait () Def run_cmd (self, cmd ): ''' Directly executes a command. It is convenient for one instance to execute multiple commands repeatedly. ''' Self. cmd = cmd Self. run () Def wait (self ): '''Wait until shell execution is complete. ''' Self. logger. debug ("waiting % s" % self. cmd) Self. ret_info, self. err_info = self. _ process. communicate () # Blocking # Returncode: A negative value-N indicates that the child was # Terminated by signal N Self. ret_code = self. _ process. returncode Self. logger. debug ("waiting % s done. return code is % d" % (self. cmd, Self. ret_code )) Def get_status (self ): ''' Get the script RUNNING status (RUNNING | FINISHED) ''' Retcode = self. _ process. poll () If retcode = None: Status = "RUNNING" Else: Status = "FINISHED" Self. logger. debug ("% s status is % s" % (self. cmd, status )) Return status # The subprocess of Python2.4 does not have send_signal, terminate, or kill # So here we need to copy one. 2.7 can be used directly with the kill () of self. _ process () Def send_signal (self, sig ): Self. logger. debug ("send signal % s to % s" % (sig, self. cmd )) OS. kill (self. _ process. pid, sig) Def terminate (self ): Self. send_signal (signal. SIGTERM) Def kill (self ): Self. send_signal (signal. SIGKILL) Def print_result (self ): Print "return code:", self. ret_code Print "return info:", self. ret_info Print "error info:", self. err_info Class RemoteShell (Shell ): ''' Remote command execution (ssh ). XXX commands with special characters may cause invalid calls, such as double quotation marks and dollar signs $ NOTE if cmd contains double quotation marks, you can use RemoteShell2 ''' Def _ init _ (self, cmd, ip ): Ssh = ("ssh-o PreferredAuthentications = publickey-o" "StrictHostKeyChecking = no-o ConnectTimeout = 10 ") # Check whether the IP address is valid or the trust relationship is not required. If there is a problem, shell reports an error. Cmd = '% s "% s"' % (ssh, ip, cmd) Shell. _ init _ (self, cmd) Class RemoteShell2 (RemoteShell ): ''' Is the same as RemoteShell, but the quotation marks are changed. ''' Def _ init _ (self, cmd, ip ): RemoteShell. _ init _ (self, cmd, ip) Self. cmd = "% s '% S'" % (ssh, ip, cmd) Class SuShell (Shell ): ''' Switch the user to execute the command (su mode ). XXX is only applicable to switching from root to other users. Because other users need to enter the password after switching, the program will be suspended. XXX commands with special characters may cause invalid calls, such as double quotation marks and dollar signs $ NOTE if cmd contains double quotation marks, you can use SuShell2 ''' Def _ init _ (self, cmd, user ): If OS. getuid ()! = 0: # direct Error Reporting for non-root users Raise Exception ('sushell must be called by root user! ') Cmd = 'Su-% s-c "% s" '% (user, cmd) Shell. _ init _ (self, cmd) Class SuShell2 (SuShell ): ''' Is the same as SuShell, but the quotation marks are changed. ''' Def _ init _ (self, cmd, user ): SuShell. _ init _ (self, cmd, user) Self. cmd = "su-% s-c '% S'" % (user, cmd) Class SuShellDeprecated (Shell ): ''' Switch the user to execute the command (in the setuid mode ). The executed function is run2, not run. XXX runs in "not clean" Mode: only users and groups are switched, and the environment variable information remains unchanged. XXX cannot obtain the ret_code, ret_info, and err_info of the command. XXX is only applicable to switching from root to other users. ''' Def _ init _ (self, cmd, user ): Self. user = user Shell. _ init _ (self, cmd) Def run2 (self ): If OS. getuid ()! = 0: # direct Error Reporting for non-root users Raise Exception ('sushell2 must be called by root user! ') Child_pid = OS. fork () If child_pid = 0: # subprocesses work Uid, gid = pwd. getpwnam (self. user) [2: 4] OS. setgid (gid) # You must set the Group first. OS. setuid (uid) Self. run () Sys. exit (0) # The sub-process exits to prevent other code from being executed. Else: # The parent process waits for the child process to exit OS. waitpid (child_pid, 0) If _ name _ = "_ main __": '''Test code ''' #1. test normal Sa = Shell ('who ') Sa. run () Sa. print_result () #2. test stderr Sb = Shell ('ls/export/dir_should_not_exists ') Sb. run () Sb. print_result () #3. test background SC = Shell ('sleep 1 ') SC. run_background () Print 'Hello from parent process' Print "return code:", SC. ret_code Print "status:", SC. get_status () SC. wait () SC. print_result () #4. test kill Import time Sd = Shell ('sleep 2 ') Sd. run_background () Time. sleep (1) Sd. kill () Sd. wait () # NOTE, still need to wait Sd. print_result () #5. test multiple command and uncompleted command output Se = Shell ('pwd; sleep 1; pwd; pwd ') Se. run_background () Time. sleep (1) Se. kill () Se. wait () # NOTE, still need to wait Se. print_result () #6. test wrong command Sf = Shell ('aaaaa ') Sf. run () Sf. print_result () #7. test instance reuse to run other command Sf. cmd = 'echo aaaaa' Sf. run () Sf. print_result () Sg = RemoteShell ('pwd', '2017. 0.0.1 ') Sg. run () Sg. print_result () # Unreachable ip Sg2 = RemoteShell ('pwd', '17. 0.0.1 ') Sg2.run () Sg2.print _ result () # Invalid ip Sg3 = RemoteShell ('pwd', '2017. 0.0.1 ') Sg3.run () Sg3.print _ result () # Ip without trust relation Sg3 = RemoteShell ('pwd', '10. 145.132.247 ') Sg3.run () Sg3.print _ result () Sh = SuShell ('pwd', 'ossuser ') Sh. run () Sh. print_result () # Wrong user Si = SuShell ('pwd', 'ossuser123 ') Si. run () Si. print_result () # User need password Si = SuShell ('pwd', 'root ') Si. run () Si. print_result () |