[To move from Bash to Ruby and Python3 I compared more processing with Bash <-> Ruby <-> Python3 (introduced on another site). ](Https://debimate.jp/2020/04/05/bashshell-script%e3%81%8b%e3%82%89ruby%e3%82%84python%e3%81%ab%e4%b9%97% e3% 82% 8a% e6% 8f% 9b% e3% 81% 88% ef% bc% 81% e9% a0% bb% e7% b9% 81% e3% 81% ab% e4% bd% bf% e3% 81% 86% e5% 87% a6% e7% 90% 86% e3% 82% 92% e5% 90% 84% e8% a8% 80 /)
This article describes what I did to replace the Bash Script with Python 3.x. The motivation for thinking about replacement was that I thought that "Bash Script over 300 Steps will strangle me."
I understand the strengths of Bash. Except for embedded environments, it is guaranteed to work with major distributions and If you write with POSIX compatibility in mind, you will have fewer corrections when porting. Familiar to many developers above all, it means a lot of people (members) who can modify the Script. But as a practical matter, I don't write Bash given the benefits mentioned above. Because I create it without thinking about it, I will see pain at a later date (example: the flow below).
Based on this reality and the advice of Google teacher (quoted below) We came to the conclusion that "Python 3.x from 2017".
Original: Source "Shell Style Guide" If you are writing a script that is more than 100 lines long, you should probably be writing it in Python instead. Bear in mind that scripts grow. Rewrite your script in another language early to avoid a time-consuming rewrite at a later date.
Translation If you're writing a script that's> 100 lines or longer, you should probably write it in Python instead. Note that the script grows. In another language to avoid time-consuming fixes later Rewrite the script as soon as possible.
-Debian 8.6 (64bit) ・ Bash (GNU bash 4.3.30) ・ Python (Version 3.5.1, Anaconda 4.0.0)
In the case of bash, you can execute it by writing the command and option as it is in Script, For Python, execute via the subprocess module.
An example of using an external command (resize command) (Bash, Python) is as follows.
sample.sh
#!/bin/bash
resize -s 30 50 #30 characters per line, 50 lines
sample.py
#!/usr/bin/env python3
-*- coding: utf-8 -*-
import subprocess
#The command you want to pass to the subprocess+List options
#State including space(run(["resize -s 30 50"]))Error if described in
subprocess.run(["resize","-s","30","50"])
An example of connecting an external command and a pipe is as follows. Compared to Bash's "|" notation, there is more content to describe.
sample.py
#!/usr/bin/env python3
import subprocess
#Connect pipes for standard output and standard error by specifying the second and third arguments
result = subprocess.Popen(["resize","-s","30","50"], \
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = result.communicate() #Acquisition of standard output and standard error
print("Standard output:%s" % out.decode('utf-8')) #If decode processing is not performed, it will be treated as a byte.
print("Standard error:%s" % err.decode('utf-8'))
An example of writing the output result of an external command to a text file is as follows. In addition, the execution result of "python sample.py" is also shown.
sample.py
#!/usr/bin/env python3
import subprocess
#Opened log file(command.log)Flow until standard output is written to
# "communicate()"of[0]部分は、複数of返り値of中から標準出力を受け取るためof記述
log_file = open("command.log", "w")
result = subprocess.Popen(["resize","-s","30","50"], stdout=subprocess.PIPE)
log_file.write(result.communicate()[0].decode('utf-8'))
log_file.close()
#If you want to add"a"Open the file by specifying.
log_file = open("command.log", "a")
result = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
log_file.write(result.communicate()[0].decode('utf-8'))
log_file.close()
command.log
COLUMNS=50;
LINES=30;
export COLUMNS LINES;
16 in total
-rw-r--r--1 nao nao 44 December 24 19:15 command.log
-rw-r--r--1 nao nao 543 December 24 15:58 python_bash.txt
-rwxr-xr-x 1 nao nao 673 December 24 19:15 sample.py
-rwxr-xr-x 1 nao nao 77 December 24 15:58 sample.sh
You will have the opportunity to use here-documents when creating sample files and templates. Below is an example of using Bash and Python here-documents.
sample.sh
#!/bin/bash
VAR="check"
#Output the contents of here document.Output to txt.
cat << EOS > output.txt
①
The scope of here documents is"<< EOS"The line following the line that passed the command(①)From
Then alone"EOS"One line above the line where(②)Until.
The EOS part can be a free character string, but
The same string must be used at the beginning and end of the here document.
$VAR #When using variables in here-documents.
\$VAR #When not output as a variable.
②
EOS
sample.py
#!/usr/bin/env python3
VAR="check"
#At the end of the here document[1:-1]If there is no, blank lines are included before and after the string.
heredoc = """
In the Python here document,
Enclose the string in three double quotes.
If you want to expand variables in a here document,
{VAR}
I will describe it as such.
And the variable in the string is the function format()Will be expanded with.
"""[1:-1].format(**locals()) # **Dictionary variables by specification{'VAR':"check"}Receive at.
output = open("output.txt", "w")
output.write(heredoc)
output.close
The following are three examples of user input acquisition.
Number of characters to enter | Echo back | Enter(Decision)Need for | |
---|---|---|---|
Example 1 | No limit | Yes | necessary |
Example 2 | No limit | None | necessary |
Example 3 | One letter | None | Unnecessary |
sample.sh
#!/bin/bash
echo -n "Please enter characters:" #option"-n"Suppresses line breaks
read USER_INPUT
echo "${USER_INPUT}"
echo -n "Input reception without echo back:"
stty -echo #Echo back OFF
read USER_INPUT
stty echo #Echo back ON
echo "${USER_INPUT}"
echo -n "One-letter input reception. No Enter input required:"
read -s -n 1 USER_INPUT #Bash only
echo "${USER_INPUT}"
sample.py
#!/usr/bin/env python3
import os
import getch # "pip install getch"It is necessary to install with.
print("Please enter characters:", end="")
user_input = input()
print(user_input)
#Besides the following methods, getpass of getpass module()If you use
#Input can be accepted without echo back.
#However, by default on the terminal"Password:"It will be displayed.
print("Input reception without echo back:", end="")
os.system("stty -echo")
user_input = input()
os.system("stty echo")
print(user_input)
print("One-letter input reception. No Enter input required:", end="")
user_input = getch.getch()
print(user_input)
Only the text colors for error message (red) and warning message (yellow) are shown below. On the Bash side, it seems that a function that can pass a string with a pipe instead of an argument is easier to use, Since it is a sample, it is in the form of passing arguments.
sample.sh
#!/bin/bash
function error_message (){
echo -n -e "\033[31m\c" #Escape sequence to make characters red
echo "$1"
echo -n -e "\033[m\c" #Restore text color
}
function warning_message (){
echo -n -e "\033[33m\c" #Escape sequence to make letters yellow
echo "$1"
echo -n -e "\033[m\c" #Restore text color
}
error_message "error"
warning_message "warning"
sample.py
#!/usr/bin/env python3
def error_message(message):
print("\033[31m%s\033[0m" % message)
def warning_message(message):
print("\033[33m%s\033[0m" % message)
error_message("error")
warning_message("warning")
The following is an example of when administrator privileges are required when executing Script.
sample.sh
#!/bin/bash
#Check the execution UID and UID,"0"(root)If so, you have administrator privileges.
# ":-"Part${EUID}If there is no value in${UID}Means to substitute the value of
if [ ${EUID:-${UID}} = 0 ]; then
echo "You have administrator privileges."
else
echo "You must have administrator privileges to run this script."
fi
sample.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
#Confirm the existence of administrator authority by acquiring UID and EUID.
if os.getuid() == 0 and os.geteuid() == 0:
print("You have administrator privileges.")
else:
print("You must have administrator privileges to run this script.")
For Bash, use getopts to interpret the options. At this time, if Script is executed using an option that is not defined, An error statement will be displayed automatically. The sample script and execution result are shown below.
sample.sh
#!/bin/bash
#getopts passes an optional character as the first argument,
#That option(Example:"d")Immediately after the option character if takes an argument":"Write(Example:"d:")。
#In the example below"d"When"f"が引数を必要Whenするオプションです。
while getopts d:f:h OPT
do
case $OPT in
d) DIR_NAME=$OPTARG #Optional arguments are stored in the variable OPTARG.
;;
f) FILE_NAME=$OPTARG
;;
h) echo "The usage of the script is xxx."
exit 0
;;
*) echo "The usage of the script is xxx." #When an option that is not specified comes
exit 1
;;
esac
done
echo "Directory name:${DIR_NAME}"
echo "file name:${FILE_NAME}"
Execution result.
$ bash sample.sh -f file_name -d directory_name
Directory name: directory_name
File name: file_name
$ bash sample.sh -f
sample.sh:Options require arguments-- f
The usage of the script is xxx.
$ bash sample.sh -k
sample.sh:Illegal option-- k
The usage of the script is xxx.
For Python3, the argparse module provides a powerful mechanism. You don't need to write a function to output help, and unlike Bash, you can use the long option. The sample script and execution result are shown below.
sample.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
#Function add_argument()Add options with.
#Two types of notation at once(Example:"-d"、"--dir")Can be registered.
#type is the type of the optional argument, dest is the optional argument(value)Write the name of the variable that stores.
#For help, the implications of this option(Text to be displayed during help)Will be described.
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--dir", type=str, dest="dir",
help="Write the directory name used in Script")
parser.add_argument("-f","--file", type=str, dest="file",
help="Write the file name used in Script")
args = parser.parse_args()
print("Directory name:%s" % args.dir)
print("file name:%s" % args.file)
Execution result.
$ python sample.py -d directory_name -f file_name
Directory name: directory_name
File name: file_name
$ python sample.py -h
usage: sample.py [-h] [-d DIR] [-f FILE]
optional arguments:
-h, --help show this help message and exit
-d DIR, --dir Write the directory name used in the DIR Script
-f FILE, --file Write the file name used in FILE Script
$ python sample.py -s
usage: sample.py [-h] [-d DIR] [-f FILE]
sample.py: error: unrecognized arguments: -s
For Bash, debugging is done in a very primitive way. In general, you're probably practicing script debugging with the following options:
option | Description |
---|---|
-u | If there is an undefined variable, the process ends. |
-v | Display the contents of Script in the order of execution. The variable name is displayed as it is. |
-x | Display the contents of Script in the order of execution. Variables can be expanded to display debug information |
As a usage, give the above option to Shebang (example: "#! / Bin / bash -x"), Insert "set -x" where you want to start debugging Script and "set + x" where you want to end it. The -x option displays a null command (:), so you can insert comments that are only visible during debugging.
The sample script and execution result are shown below.
sample.sh
#!/bin/bash -x
START="Start script"
END="End of script"
echo ${START} #Variables are displayed in an expanded state
set +x #Canceling options
:This null command is not displayed
echo "This echo command outputs only the character string part"
set -x #Option activation
:This null command is displayed
echo "${END}"
Execution result.
$ ./sample.sh
+ START=Start script
+ END=End of script
+start echo script
Start script
+ set +x
This echo command outputs only the character string part
+ :This null command is displayed
+end echo script
End of script
For Python3, debugging is possible with the debugger pdb. To use it, insert "import pdb; pdb.set_trace ()" where you want to debug and The debugger starts when the Script reaches the above insertion position.
Since I am not using pdb myself, I will write an article that I referred to this time (I will add it in the future). ・ "Pdb — Python Debugger" ・ "Python Debugging Tips (@TakesxiSximada)" ・ "Introduction to Efficient Debugging in Python"
It's a very basic content, but because there were "items that took longer to investigate than expected" I will leave it as a memorandum.
In terms of replacing Bash with Python ・ "What is the equivalent of Bash trap (signal detection)?" -"Which Python function corresponds to basic commands such as cd / pwd / mkdir in the first place?" ・ "Is it easier to process files with sed or awk, or should I use Python?" And there is no end to what to look for. At a later date, this content will also be published in Qiita.
Python 3.x instead of Bash. It's been half a year since I came up with that idea. In the last six months, I've had a painful eye with Python only once. The Python 3 I created didn't work.
Migrate Python scripts from the development environment (Debian 8.8) to another verification environment (CentOS 7.x) When I tried to run it on CentOS, the script didn't work. The reason is that CentOS 7.x uses 2.7 by default. Moreover, because it is a verification environment, it is not possible to install a new package (Python3). I've rewritten Python to work from 3.x to 2.7.
From this experience, I gained the knowledge that "after checking the execution environment, write the script". It's important, Runtime.
Recommended Posts