{"id":1465,"date":"2025-01-10T04:41:08","date_gmt":"2025-01-10T04:41:08","guid":{"rendered":"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/"},"modified":"2025-01-10T04:41:08","modified_gmt":"2025-01-10T04:41:08","slug":"comparing-bash-and-python-for-linux-scripting","status":"publish","type":"post","link":"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/","title":{"rendered":"Comparing Bash and Python for Linux scripting"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div id=\"\">\n<p>Sh (from English shell) is a mandatory command interpreter for UNIX-compatible systems according to the POSIX standard. However, its capabilities are limited, so more feature-rich command interpreters such as Bash or Ksh are often used instead. Ksh is typically used in BSD-family operating systems, while Bash is used in Linux-family operating systems. Command interpreters simplify solving small tasks related to working with processes and files. This article will focus on Linux operating systems, so the discussion will revolve around Bash.<\/p>\n<p>Python, on the other hand, is a full-fledged interpreted programming language, often used for writing scripts or solving small application tasks. It is hard to imagine a modern UNIX-like system without both sh and Python, unless it is a device with a minimalist OS like a router. For example, in Ubuntu Oracular, the <code>python3<\/code> package cannot be removed because the <code>grub-common<\/code> package depends on it, which in turn depends on <code>grub2-common<\/code> and, consequently, <code>grub-pc<\/code>, the actual operating system bootloader. Thus, Python 3 can confidently be used as a replacement for Bash when necessary.<\/p>\n<p>When solving various tasks at the OS or file system level, the question may arise: which language, Bash or Python, is more advantageous to use in a particular case? The answer depends on the task at hand. Bash is advantageous when you need to quickly solve a simple task related to process management, file search, or modification. However, as the logic becomes more complex, Bash code can become cumbersome and difficult to read (although readability primarily depends on the programmer). Of course, you can break the code into scripts and functions, create sh-libraries, and connect them via the <code>source<\/code> command, but covering them with modular tests becomes challenging.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Turinys:<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Preface\" >Preface<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Who_is_this_article_for\" >Who is this article for?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Debugging_Scripts\" >Debugging Scripts<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Debugging_in_Bash\" >Debugging in Bash<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Debugging_via_xtrace\" >Debugging via xtrace<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Debugging_via_trap\" >Debugging via trap<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Debugging_in_Python\" >Debugging in Python<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Debugging_via_pdb\" >Debugging via pdb<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Logging_via_the_logging_module\" >Logging via the logging module<\/a><\/li><\/ul><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Comparison_of_Bash_and_Python_Semantics\" >Comparison of Bash and Python Semantics<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Variables_and_Data_Types\" >Variables and Data Types<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Primitive_Data_Types\" >Primitive Data Types<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#String_Formatting\" >String Formatting<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Arrays\" >Arrays<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Associative_Arrays\" >Associative Arrays<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Module_Importing\" >Module Importing<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Conditionals_and_Loops\" >Conditionals and Loops<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Conditional_Operator\" >Conditional Operator<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Loops\" >Loops<\/a><ul class='ez-toc-list-level-5' ><li class='ez-toc-heading-level-5'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Loop_with_Element_Iteration\" >Loop with Element Iteration<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-5'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Loop_with_Counter\" >Loop with Counter<\/a><\/li><\/ul><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Functions\" >Functions<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Input_Output_and_Error_Streams\" >Input, Output, and Error Streams<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Writing_to_a_File\" >Writing to a File<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Writing_Multi-line_Text_to_a_File\" >Writing Multi-line Text to a File<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-26\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Reading_from_a_File\" >Reading from a File<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-27\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Stream_Redirection\" >Stream Redirection<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-28\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Executing_External_Commands\" >Executing External Commands<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-29\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Getting_Command_Output\" >Getting Command Output<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-30\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Getting_and_Processing_Return_Codes\" >Getting and Processing Return Codes<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-31\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Executing_a_Command_with_Only_Getting_the_Return_Code\" >Executing a Command with Only Getting the Return Code<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-32\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Exceptions_Instead_of_Handling_Return_Codes\" >Exceptions Instead of Handling Return Codes<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-33\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Building_Pipelines\" >Building Pipelines<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-34\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Pipelines_with_Parallel_Data_Processing\" >Pipelines with Parallel Data Processing<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-35\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Parallel_Process_Execution_with_Waiting_for_Completion\" >Parallel Process Execution with Waiting for Completion<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-36\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Process_Substitution\" >Process Substitution<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-37\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Environment_Variables\" >Environment Variables<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-38\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Working_with_Environment_Variables\" >Working with Environment Variables<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-39\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Setting_Values_for_Individual_Processes\" >Setting Values for Individual Processes<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-40\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Executing_Arbitrary_Code\" >Executing Arbitrary Code<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-41\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Working_with_the_File_System_and_Processes\" >Working with the File System and Processes<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-42\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Getting_and_Changing_the_Current_Directory\" >Getting and Changing the Current Directory<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-43\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Working_with_Signals\" >Working with Signals<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-44\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Compilation_Capability\" >Compilation Capability<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-45\" href=\"https:\/\/infonaujiena.lt\/index.php\/2025\/01\/10\/comparing-bash-and-python-for-linux-scripting\/#Choosing_a_Language_Depending_on_the_Task\" >Choosing a Language Depending on the Task<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Preface\"><\/span>Preface<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"Who_is_this_article_for\"><\/span>Who is this article for?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>This article is for those who are interested in system administration, are familiar with one of the two languages, and want to understand the other. Or for those who want to learn about some features of Bash and Python that they might not have known before. Basic command-line skills and familiarity with programming fundamentals are required to understand the material.<\/p>\n<p>For a complete picture, including code readability, the article will compare debugging capabilities, syntax, and various use cases. Similar examples in both languages will be provided. In Python code, you may occasionally see commas at the end of enumerations\u2014this is not an error. Such a style is considered good practice because it avoids marking the last element as modified when adding new elements to the enumeration.<\/p>\n<p>The article will consider Bash version 3.0 or higher and Python version 3.7 or higher.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Debugging_Scripts\"><\/span>Debugging Scripts<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Both languages are interpreted, meaning that during script execution, the interpreter knows a lot about the current execution state.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Debugging_in_Bash\"><\/span>Debugging in Bash<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"Debugging_via_xtrace\"><\/span>Debugging via xtrace<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Bash supports the <code>xtrace<\/code> option (<code>-x<\/code>), which can be set either in the command line when starting the interpreter or within the script itself:<\/p>\n<pre><code class=\"language-bash\">#!\/bin\/bash\n\n# Specify where to write logs, open the file for writing:\nexec 3&gt;\/path\/to\/log\/file\nBASH_XTRACEFD=3  # which file descriptor to output debug information to\n\nset -x # enable debugging\n# ... code to debug ...\nset +x # disable debugging\n<\/code><\/pre>\n<p>Such logs can also be written to the systemd journal if implementing a simple service:<\/p>\n<pre><code class=\"language-bash\">#!\/bin\/bash\n\n# Specify where to write logs:\nexec 3&gt; &gt;(systemd-cat --priority=debug)\nBASH_XTRACEFD=3  # which stream to output debug information to\n\nset -x # enable debugging\n# ... code to debug ...\nset +x # disable debugging\n<\/code><\/pre>\n<p>Debugging in Bash will show which commands are being executed and with which arguments. If you need to get the current values of variables or the code of executed functions, you can do this with the <code>set<\/code> command without arguments. However, since the output of the command can be quite large, <code>set<\/code> is more suitable for manual debugging than for event logging.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Debugging_via_trap\"><\/span>Debugging via trap<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Another debugging method is setting handlers for command execution using the <code>trap<\/code> command on the special &#8222;trap&#8221; DEBUG. The commands being executed can be obtained through the built-in variable <code>BASH_COMMAND<\/code>. However, you cannot get the return code from this handler because it is executed before the command itself is called.<\/p>\n<pre><code class=\"language-bash\">trap 'echo \"+ ${BASH_COMMAND}\"' DEBUG\n<\/code><\/pre>\n<p>But it will be more useful to intercept errors and output the command and line number where the error occurred. To inherit this interception by functions, you also need to set the <code>functrace<\/code> option:<\/p>\n<pre><code class=\"language-bash\">set -o functrace\ntrap 'echo \"+ line ${LINENO}: ${BASH_COMMAND} -&gt; $?\"' ERR\n\n# Test:\nls \"${PWD}\"\nls unknown_file\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Debugging_in_Python\"><\/span>Debugging in Python<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"Debugging_via_pdb\"><\/span>Debugging via pdb<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Python has rich debugging and logging tools. For debugging, Python has the <code>pdb<\/code> module. You can run a script with debugging enabled from the console, in which case the debug mode will be activated in exceptional situations:<\/p>\n<pre><code class=\"language-bash\">python3 -m pdb my_script.py\n<\/code><\/pre>\n<p>Directly in the code, you can set breakpoints using the built-in <code>breakpoint()<\/code> function.<\/p>\n<pre><code class=\"language-python\">#!\/usr\/bin\/python3\n\nimport os\n\nbreakpoint()\n# Now you can try, for example, the source os command:\n# (Pdb) source os\n<\/code><\/pre>\n<p>The language is object-oriented, and everything in it is an object. You can see the methods available for an object using the <code>dir()<\/code> command. For example, <code>dir(1)<\/code> will show the methods available for the object <code>1<\/code>. Example of calling one of these methods: <code>(1).bit_length()<\/code>. In many cases, this helps to understand arising questions without the need to read the documentation. In debug mode, you can also use the <code>dir()<\/code> command to get information about objects and <code>print()<\/code> to get variable values.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Logging_via_the_logging_module\"><\/span>Logging via the logging module<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Python provides the <code>logging<\/code> module, which allows you to log debug information with specified logging levels and log sources. In general, logging looks something like this:<\/p>\n<pre><code class=\"language-python\">import logging\n\nlogging.basicConfig(\n    filename=\"myscript.log\",\n    level = logging.DEBUG, # output DEBUG, INFO, WARNING, ERROR, and CRITICAL levels\n)\n\nlogger = logging.getLogger('MyApp')\n\nlogger.debug('Some debug information')\nlogger.error('Some error')\n<\/code><\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Comparison_of_Bash_and_Python_Semantics\"><\/span>Comparison of Bash and Python Semantics<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"Variables_and_Data_Types\"><\/span>Variables and Data Types<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"Primitive_Data_Types\"><\/span>Primitive Data Types<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, all variables are strings, but string variables can also be used as numbers. For arithmetic calculations, the syntax <code>$(( expression ))<\/code> is used.<\/p>\n<pre><code class=\"language-bash\">str_var=\"some_value\"  # string, array of characters\n\nint_var=1234  # string \"1234\", but can be used in calculations\nint_var=$(( 1 + (int_var - 44) \/ 111 - 77 ))  # string: \"-66\"\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">str_var=\"some_value\"  # class str\nint_var = 1234  # class int\nint_var = 1 + (int_var - 44) \/\/ 111 - 77  # -66, class int\n<\/code><\/pre>\n<p>Floating-point numbers are not supported in Bash. And this is logical, because if you need to use floating-point numbers in command-line scripts, you are clearly doing something at the wrong level or in the wrong programming language. However, floating-point numbers are supported in Ksh.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"String_Formatting\"><\/span>String Formatting<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Both Bash and Python support variable substitution in formatted strings. In Bash, formatted strings are strings enclosed in quotes, while in Python, they are strings with the <code>f<\/code> prefix.<\/p>\n<p>Both languages also support C-like style output of formatted strings. In Bash, this way you can even format floating-point numbers, although the language itself does not support them (the decimal separator is determined by the locale).<\/p>\n<pre><code class=\"language-bash\">var1='Some string'\nvar2=0,5\necho \"Variable 1: ${var1}, variable 2: ${var2}\"\n# Variable 1: Some string, variable 2: 0,5\n\n# Without the current locale\nLANG=C \\\nprintf 'String: %s, number: %d, floating-point number: %f.\\n' \\\n        'str' '1234' '0.1'\n\n# With the current locale\nprintf 'String: %s, number: %d, floating-point number: %f.\\n' \\\n        'str' '1234' '0,1'\n# String: str, number: 1234, floating-point number: 0,100000.\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">var1 = 'Some string'\nvar2 = 0.5\nprint(f\"Variable 1: {var1}, variable 2: {var2}\")\n# Variable 1: Some string, variable 2: 0.5\n\n# Without the current locale:\nprint('String: %s, number: %d, floating-point number: %f.'\n        % ('str', 1234, 0.1))\n# String: str, number: 1234, floating-point number: 0.100000.\n\n# With the current locale:\nimport locale\nlocale.setlocale('')  # apply the current locale\nprint(locale.format_string('String: %s, number: %d, floating-point number: %f.',\n        ('str', 1234, 0.1)))\n# String: str, number: 1234, floating-point number: 0,100000.\n<\/code><\/pre>\n<p>You can notice a difference regarding the locale\u2014in Python, the <code>print()<\/code> function ignores the locale. If you need to output values considering the locale, you must use the <code>locale.format_string()<\/code> function.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Arrays\"><\/span>Arrays<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, arrays are essentially text separated by spaces (by default). The syntax is very specific; for example, to copy an array (via <code>@<\/code>), you must enclose all its elements in quotes; otherwise, any spaces in the elements themselves will cause the element to be split into parts. But in general, working with arrays is similar in simple cases:<\/p>\n<pre><code class=\"language-bash\">arr=( 'First item' 'Second item' 'Third item' )\necho \"${arr(0)}\" \"${arr(1)}\" \"${arr(2)}\"\narr_copy=\"${arr(@)}\"  # copying the array, quotes are mandatory\narr(0)=1\narr(1)=2\narr(2)=3\necho \"${arr(@)}\"\necho \"${arr_copy(0)}\" \"${arr_copy(1)}\" \"${arr_copy(2)}\"\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">arr = ( 'First', 'Second', 'Third' )\nprint(arr(0), arr(1), arr(2))\narr_copy = arr.copy()  # but you can also do it like in Bash: ( *arr )\narr(0) = 1\narr(1) = 2\narr(2) = 3\nprint(*arr)\nprint(arr_copy(0), arr_copy(1), arr_copy(2))\n<\/code><\/pre>\n<p>The <code>*<\/code> operator in Python performs unpacking of lists, dictionaries, iterators, etc. That is, the elements of the array are listed as if they were separated by commas as arguments.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Associative_Arrays\"><\/span>Associative Arrays<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Bash also supports associative arrays (unlike Sh), but the capabilities for working with them are limited. In Python, associative arrays are called dictionaries, and the language provides very rich capabilities for working with them.<\/p>\n<pre><code class=\"language-bash\">declare -A assoc_array=(\n  (name1)='Value 1'\n  (name2)='Value 2'\n  (name3)='Value 3'\n)\n\n# Assigning a value by key:\nassoc_array('name4')='Value 4'  # assigning a value\n\n# Element-wise access:\necho \"${assoc_array('name1')}\" \\\n        \"${assoc_array('name2')}\" \\\n        \"${assoc_array('name3')}\" \\\n        \"${assoc_array('name4')}\"\n\necho \"${!assoc_array(@)}\" # output all keys\necho \"${assoc_array(@)}\" # output all values\n\n# Iterate over all elements\nfor key in \"${!assoc_array(@)}\"; do\n    echo \"Key: ${key}\"\n    echo \"Value: ${assoc_array($key)}\"\ndone\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">assoc_array = {\n  'name1': 'Value 1',\n  'name2': 'Value 2',\n  'name3': 'Value 3',\n}\n\n# Assigning a value by key:\nassoc_array('name4') = 'Value 4'\n\n# Element-wise access\nprint(\n    assoc_array('name1'),\n    assoc_array('name2'),\n    assoc_array('name3'),\n    assoc_array('name4')\n)\n\nprint(*assoc_array)  # output all keys\nprint(*assoc_array.values())  # output all values\n\nfor key, value in assoc_array.items():\n    print(f\"Key: {key}\")\n    print(f\"Value: {value}\")\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Module_Importing\"><\/span>Module Importing<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In Bash, there are no modules as such. But you can execute a script in the current interpreter using the <code>source<\/code> command. Essentially, this is analogous to importing modules, since all functions of the included script become available in the current interpreter&#8217;s namespace. In Python, there is full support for modules with the ability to import them. Moreover, the Python standard library contains a large number of modules for a wide variety of use cases. Essentially, what is implemented in Bash by third-party command-line utilities may be available in Python as standard library modules (and if not, you can install additional libraries).<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Include the file mylib.sh with some functions:\nsource mylib.sh\n\n# Let's see the list of available functions (all of them):\ndeclare -F\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\"># Import the module mylib.py or mylib.pyc:\nimport mylib\n\n# Let's see the list of available objects in the mylib module:\nprint(dir(mylib))\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Conditionals_and_Loops\"><\/span>Conditionals and Loops<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"Conditional_Operator\"><\/span>Conditional Operator<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, conditions work on two principles: either a command is provided as a condition, and its return code is checked, or built-in Bash double square or double round brackets are used. In the case of a return code, 0 is true (everything is fine), while in the case of double round brackets, it&#8217;s the opposite\u2014the result of an arithmetic expression is checked, where 0 is false.<\/p>\n<p>In Python, the standard approach for programming languages is used: <code>False<\/code>, <code>0<\/code>, <code>''<\/code>, <code>()<\/code>, <code>set()<\/code>, <code>{}<\/code>\u2014all of these are equated to <code>False<\/code>. Non-empty, non-zero values are equated to <code>True<\/code>.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">if (( \"${PWD}\" == \"${HOME}\" )); then\n    echo 'Current directory: ~'\nelif (( \"${PWD}\" == \"${HOME}\"* )); then\n    echo \"Current directory: ~${PWD#${HOME}}\"\nelse\n    echo \"Current directory: ${PWD}\"\nfi\n\nif (( UID &lt; 1000 )); then\n    echo \"You are logged in as a system user. Please log in as yourself.\"\nfi\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import os\n\ncurr_dir = os.environ('PWD')\nhome_dir = os.environ('HOME')\n\nif curr_dir == home_dir:\n    print('Current directory: ~')\nelif curr_dir.startswith(home_dir):\n    print('Current directory: ~' + curr_dir(len(home_dir):))\nelse:\n    print(f\"Current directory: {curr_dir}\")\n\nif os.environ('UID') &lt; 1000:\n    print('You are logged in as a system user. Please log in as yourself.')\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Loops\"><\/span>Loops<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Both languages support <code>for<\/code> and <code>while<\/code> loops.<\/p>\n<h5><span class=\"ez-toc-section\" id=\"Loop_with_Element_Iteration\"><\/span>Loop with Element Iteration<span class=\"ez-toc-section-end\"><\/span><\/h5>\n<p>In both languages, the <code>for<\/code> loop supports element iteration via the <code>in<\/code> operator. In Bash, elements of an array or elements of a string separated by separators recorded in the <code>IFS<\/code> variable (default: space, tab, and newline) are iterated. In Python, the <code>in<\/code> operator allows iterating over any iterable objects, such as lists, sets, tuples, and dictionaries, and is safer to work with.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Recoding text files from CP1251 to UTF-8\nfor filename in *.txt; do\n    tmp_file=`mktemp`\n    iconv -f CP1251 -t UTF-8 \"${filename}\" -o \"${tmp_file}\"\n    mv \"${tmp_file}\" \"${filename}\"\ndone\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import glob\nfrom pathlib import Path\n\n# Recoding text files from CP1251 to UTF-8\nfor filename in glob.glob('*.txt'):\n    file = Path(filename)\n    text = file.read_text(encoding='cp1251')\n    file.write_text(text, encoding='utf8')\n<\/code><\/pre>\n<h5><span class=\"ez-toc-section\" id=\"Loop_with_Counter\"><\/span>Loop with Counter<span class=\"ez-toc-section-end\"><\/span><\/h5>\n<p>A loop with a counter in Bash looks unusual; the form for arithmetic calculations is used (<code>((initialization; conditions; actions after iteration))<\/code>).<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Get a list of all locally registered hosts:\nmapfile -t lines &lt; &lt;(grep -P -v '(^\\s*$|^\\s*#)' \/etc\/hosts)\n\n# Output the list with numbering:\nfor ((i = 0; i &lt; \"${#lines(@)}\"; i += 1)); do\n    echo \"$((i + 1)). ${lines($i)}\"\ndone\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">from pathlib import Path\nimport re\n\ndef is_host_line(s):\n    return not re.match(r'(^\\s*$|^\\s*#)', s)\n\nlines = list(filter(is_host_line, Path('\/etc\/hosts').read_text().splitlines()))\n\nfor i in range(0, len(lines)):\n    print(f\"{i + 1}. {lines(i)}\")\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Functions\"><\/span>Functions<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>As in regular languages, Bash supports functions. In essence, functions in Bash are similar to separate scripts\u2014they can also accept arguments like regular scripts, and they return a return code. But, unlike Python, they cannot return a result other than a return code. However, you can return text through the output stream.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">some_function()\n{\n    echo \"Script: $0.\"\n    echo \"Function: ${FUNCNAME}.\"\n    echo \"Function arguments:\"\n    for arg in \"$@\"; do\n        echo \"${arg}\"\n    done\n\n    return 0\n}\n\nsome_function One Two Three Four Five\necho $? # Return code\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import inspect\n\ndef some_function_is_ok(*args):\n    try:  # If suddenly run from the interpreter\n        script_name = __file__\n    except:\n        script_name=\"\"\n    print('Script: ' + script_name)\n    print('Function: ' + inspect.getframeinfo(inspect.currentframe()).function)\n    print('Function arguments:')\n    print(*args, sep='\\n')\n    return True\n\nresult = some_function_is_ok('One', 'Two', 'Three', 'Four', 'Five')\nprint(result) # True\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Input_Output_and_Error_Streams\"><\/span>Input, Output, and Error Streams<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The input stream is used to receive information by a process, while the output stream outputs information. Why streams and not regular variables? Because in streams, information can be processed as it arrives. Since information from the output stream can undergo further processing, error messages can break this information. Therefore, errors are output to a separate error stream. However, when running a command in interactive mode, these streams are mixed. Since these are streams, they can be redirected, for example, to a file. Or vice versa, read a file into the input stream. In Bash, the input stream has the number 0, the output stream\u20141, and the error stream\u20142. If the stream number is not specified in the redirection operator to a file, the output stream is redirected.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Writing_to_a_File\"><\/span>Writing to a File<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Writing to a file in Bash is done using the <code>&gt;<\/code> operator, which redirects the command&#8217;s output to the specified file. In Python, you can write text files using the <code>pathlib<\/code> module or standard means\u2014by opening a file via the <code>open()<\/code> function. The latter option is more complex but is well-known to programmers.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Clear a text file by redirecting an empty string to it:\necho -n &gt; some_text_file.txt\n\n# Write a line to a file, overwriting it:\necho 'Line 1' &gt; some_other_text_file.txt\n\n# Append a line to a file:\necho 'Line 2' &gt;&gt; some_other_text_file.txt\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">from pathlib import Path\n\n# Overwrite the file with an empty string (make it empty):\nPath('some_text_file.txt').write_text('')\n\n# Overwrite the file with a line:\nPath('some_other_text_file.txt').write_text('Line 1')\n\n# Open the file for appending (a):\nwith open('some_other_text_file.txt', 'a') as fd:\n    print('Line 2', file=fd)\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Writing_Multi-line_Text_to_a_File\"><\/span>Writing Multi-line Text to a File<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>For multi-line text in Bash, there is a special heredoc format (an arbitrary label after <code>&lt;&lt;&lt;<\/code>, the repetition of which on a new line will mean the end of the text), which allows redirecting arbitrary text to the command&#8217;s input stream, and from the command, it can be redirected to a file (and here you can&#8217;t do without the external <code>cat<\/code> command). Redirecting file contents to a process is much simpler.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Redirect multi-line text to a file for appending:\ncat &lt;&lt;&lt;EOF &gt;&gt; some_other_text_file.txt\nLine 3\nLine 4\nLine 5\nEOF\n\n# Redirect file contents to the cat command:\ncat &lt; some_other_text_file.txt\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\"># Open the file for appending (w+):\nwith open('some_other_text_file.txt', 'w+') as fd:\n    print(\"\"\"Line 3\nLine 4\nLine 5\"\"\", file=fd)\n\n# Open the file for reading (r):\nwith open('some_other_text_file.txt', 'r') as fd:\n    # Output the file contents line by line:\n    for line in fd:\n        print(line)\n    # Or fd.read(), but then the entire file will be read into memory.\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Reading_from_a_File\"><\/span>Reading from a File<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, reading from a file is done via the <code>&lt;<\/code> sign. In Python, you can read in the standard way via <code>open()<\/code>, or simply via <code>Path(...).read_text()<\/code>:<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">cat &lt; some_other_text_file.txt\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import pathlib\n\nprint(Path('some_other_text_file.txt').read_text())\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Stream_Redirection\"><\/span>Stream Redirection<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Streams can be redirected not only to a file or process but also to another stream.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">error()\n{\n    # Redirect the output stream and error stream to the error stream (2).\n    &gt;&amp;2 echo \"$@\"\n}\n\nerror 'An error occurred.'\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">print('An error occurred.', file=sys.stderr)\n<\/code><\/pre>\n<p>In simple cases, redirection to a file or from a file in Bash looks much clearer and simpler than writing to a file or reading from it in Python. However, in complex cases, Bash code will be less understandable and more difficult to analyze.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Executing_External_Commands\"><\/span>Executing External Commands<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Running external commands in Python is more cumbersome than in Bash. Although, of course, there are simple functions <code>subprocess.getoutput()<\/code> and <code>subprocess.getstatusoutput()<\/code>, but they lose the advantage of Python in terms of passing each individual argument as a list element.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Getting_Command_Output\"><\/span>Getting Command Output<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>If you simply need to get text from a command and you are sure that it will always work, you can do it as follows:<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">cmd_path=\"`which ls`\"  # backticks execute the command and return its output\necho \"${cmd_path}\"  # output the command path\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\ncmd_path = subprocess.getoutput(\"which ls\").rstrip('\\n')\nprint(cmd_path)  # output the path to the ls command\n<\/code><\/pre>\n<p>But getting command output via backticks in Bash will be incorrect if you need to get an array of lines. In Python, <code>subprocess.getoutput()<\/code> accepts a command line, not an array of arguments, which carries some risks when substituting values. And both options do not ignore the return code of the executed command.<\/p>\n<p>Running a utility in Python to get some list into a variable will take much more code than in Bash, although the code in Python will be much clearer and simpler:<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">mapfile -t root_files &lt; &lt;(ls \/)  # put the list of files from \/ into root_files\necho \"${root_files(@)}\"  # Output the list of files\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\nresult = subprocess.run(\n        ('ls', \"https:\/\/techplanet.today\/\"),  # we are sure that such a command exists\n        capture_output = True,  # get the command output\n        text = True,  # interpret input and output as text\n)\nroot_files = result.stdout.splitlines()  # get lines from the output\nprint(*root_files, sep='\\n')  # output one file per line\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Getting_and_Processing_Return_Codes\"><\/span>Getting and Processing Return Codes<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>With full error handling, it becomes even more complicated, adding checks that complicate the code:<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">root_files=\"`ls \/some\/path`\"  # Run the command in backticks\nif (( $? != 0 )); then\n    exit $?\nfi\necho \"${root_files(@)}\"  # Output the list of files\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\nimport sys\n\nresult = subprocess.run(\n        ('ls', '\/some\/path'),\n        capture_stdout = True,  # get the command output\n        text = True,  # interpret input and output as text\n        shell = True, # to get the return code, not an exception, if the command does not exist\n)\nif result.returncode != 0:\n    sys.exit(result.returncode)\nroot_files = result.stdout.split('\\n')  # get lines from the output\ndel root_files(-1)  # the last line will be empty due to \\n at the end, delete it\nprint(*root_files, sep='\\n')  # output one file per line\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Executing_a_Command_with_Only_Getting_the_Return_Code\"><\/span>Executing a Command with Only Getting the Return Code<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Executing a command with only getting the return code is slightly simpler:<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">any_command any_arg1 any_arg2\nexit_code=$? # get the return code of the previous command\nif (( $exit_code != 0 )); then\n    exit 1\nfi\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\nimport sys\n\nresult = subprocess.run(\n    (\n        'any_command',\n        'any_arg1',\n        'any_arg2',\n    ),\n    shell = True,  # to get the error code of a non-existent process, not an exception\n)\nif result.returncode != 0:\n    sys.exit(1)\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Exceptions_Instead_of_Handling_Return_Codes\"><\/span>Exceptions Instead of Handling Return Codes<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>But everything becomes even simpler if the script exit mode on any error is enabled. In Python, this approach is used by default; errors do not need to be checked manually; a function can throw an exception and crash the process.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">set -o errexit  # crash on command errors\nset -o pipefail  # the entire pipeline fails if there is an error inside the pipeline\n\ncritical_command any_arg1 any_arg2\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\n\nsubprocess.run(\n    (\n        'critical_command',\n        'any_arg1',\n        'any_arg2',\n    ),\n    check = True, # throw an exception on a non-zero return code\n)\n<\/code><\/pre>\n<p>In some cases, exceptions can be caught and handled. In Python, this is done via the <code>try<\/code> operator. In Bash, such catches are done via the usual <code>if<\/code> operator.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">set -o errexit  # crash on command errors\nset -o pipefail  # the entire pipeline fails if there is an error inside the pipeline\n\nif any_command any_arg1 any_arg2; then\n    do_something_else any_arg1 any_arg2\nfi\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\n\ntry:\n    subprocess.run(\n        (\n            'critical_command',\n            'any_arg1',\n            'any_arg2',\n        ),\n        check = True,  # throw an exception on a non-zero return code\n    )\nexcept:\n    subprocess.run(\n        (\n            'do_something_else',\n            'any_arg1',\n            'any_arg2',\n        ),\n        check = True,  # throw an exception on a non-zero return code\n    )\n<\/code><\/pre>\n<p>In high-level languages, error handling via exceptions is preferred. The code becomes simpler and clearer, meaning there is less chance of making a mistake, and code review becomes cheaper. Although sometimes such checks look more cumbersome than a simple return code check. Whether to use this style of error handling largely depends on whether such exception checks will be frequent or will be in exceptional cases.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Building_Pipelines\"><\/span>Building Pipelines<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In Bash, pipelines are common practice, and the language itself has syntax for creating pipelines. Since Python is not a command interpreter, it is done a bit more cumbersome via the <code>subprocess<\/code> module.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">ls | grep -v '\\.txt$' | grep 'build'\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\n\np1 = subprocess.Popen(\n    ('ls'),\n    stdout = subprocess.PIPE,  # to pass output to the next command\n    text = True,\n)\n\np2 = subprocess.Popen(\n    (\n        'grep',\n        '-v',\n        '\\\\.txt$'\n    ),\n    stdin = p1.stdout,  # create a pipeline\n    stdout = subprocess.PIPE,  # to pass output to the next command\n    text = True,\n)\n\np3 = subprocess.Popen(\n    (\n        'grep',\n        'build',\n    ),\n    stdin = p2.stdout,  # create a pipeline\n    stdout = subprocess.PIPE,  # already for reading from the current process\n    text = True,\n)\n\nfor line in p3.stdout:  # read line by line as data arrives\n    print(line, end='')  # each line already ends with \\n\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Pipelines_with_Parallel_Data_Processing\"><\/span>Pipelines with Parallel Data Processing<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, pipelines can be created both between commands and between commands and interpreter blocks. For example, you can redirect a pipeline to a line-by-line reading loop. In Python, processing data from a parallel process is also done by simple line-by-line reading from the process&#8217;s output stream.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Get a list of files containing some text:\nfind . -name '*.txt' \\\n    | while read line; do  # sequentially get file paths\n        if (( \"${line}\" == *'text'* )); then  # substring in string\n            echo \"${line}\"\n        fi\n    done\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import subprocess\n\np = subprocess.Popen(\n    (\n        'find',\n        '.',\n        '-name',\n        '*.txt'\n    ),\n    stdout=subprocess.PIPE,\n    text=True,\n)\n\nwhile True:\n    line = p.stdout.readline().rstrip('\\n')  # there is always \\n at the end\n    if not line:\n        break\n    if 'text' in line:  # substring in string\n        print(line)\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Parallel_Process_Execution_with_Waiting_for_Completion\"><\/span>Parallel Process Execution with Waiting for Completion<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, running a process in the background is supported at the language syntax level (the <code>&amp;<\/code> operator), and you can run both individual commands in the background and parts of the interpreter (for example, functions or loops). But at this level of complexity, the code will often be simpler and clearer if it is written in Python, especially since the standard library provides capabilities that at the command interpreter level are implemented by third-party utilities that need to be considered as dependencies.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">unalias -a  # in case someone copies directly into the terminal\n\nget_size_by_url()\n{\n    url=\"$1\"\n    # Get the file size from the Content-Length field of the response headers to a HEAD request\n    curl --head --silent --location \"${url}\" \\\n        | while read -r line; do\n            # Find the size in the headers using a regular expression\n            if (( \"${line}\" =~ ^Content-Length:((:space:))*(.+)((:space:))+$ )); then\n                echo -n \"${BASH_REMATCH(1)}\"  ## 1 corresponds to the first opening bracket\n                return 0\n            fi\n        done\n}\n\ndownload_range()\n{\n    url=\"$1\"\n    start=$2\n    end=$3\n    output_file=\"$4\"\n    ((curr_size = end - start + 1))\n    curl \\\n            --silent \\\n            --show-error \\\n            --range \"${start}-${end}\" \\\n            \"${url}\" \\\n            --output - \\\n        | dd \\\n            of=\"${output_file}\" \\\n            oflag=seek_bytes \\\n            seek=\"${start}\" \\\n            conv=notrunc\n}\n\ndownload_url()\n{\n    url=\"$1\"\n    output_file=\"$2\"\n    \n    ((file_size = $(get_size \"${url}\")))\n    # Allocate disk space for the file in advance:\n    fallocate -l \"${file_size}\" \"${output_file}\"\n\n    range_size=10485760  # 10 MiB\n     # Divide into parts of up to 100 MiB:\n    ((ranges_count = (file_size + range_size - 1) \/ range_size))\n    declare -a pids  ## We will save all process identifiers\n    for ((i = 0; i &lt; ranges_count; i += 1)); do\n        ((start = i * range_size))\n        ((end = (i + 1) * range_size - 1))\n        if ((end &gt;= file_size)); then\n            ((end = file_size - 1))\n        fi\n        # Start downloading in the background:\n        download_range \"${url}\" $start $end \"${output_file}\" &amp;\n        pids($i)=$!  # remember the PID of the background process\n    done\n    \n    wait \"${pids(@)}\"  # wait for the processes to complete\n}\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import requests\nfrom multiprocessing import Process\nimport os\n\n\ndef get_size_by_url(url):\n    response = requests.head(url)\n    return int(response.headers('Content-Length'))\n\ndef download_range(url, start, end, output_file):\n    req = requests.get(\n        url,\n        headers = { 'Range': 'bytes=\" + str(start) + \"-' + str(end) },\n        stream = True,\n    )\n    req.raise_for_status()\n\n    with open(output_file, 'r+b') as fd:\n        fd.seek(start)\n        for block in req.iter_content(4096):\n            fd.write(block)\n\ndef download_url(url, output_file):\n    file_size = get_size_by_url(url)\n    range_size = 10485760  # 10 MiB\n    ranges_count = (file_size + range_size - 1) \/\/ range_size\n\n    with open(output_file, 'wb') as fd:\n        # Allocate space for the file in advance:\n        os.posix_fallocate(fd.fileno(), 0, file_size)\n\n    processes = ()\n    for i in range(ranges_count):\n        start = i * range_size\n        end = start + range_size - 1\n        if end &gt;= file_size:\n            end = file_size - 1\n\n        # Prepare the process and run it in the background:\n        process = Process(\n            target = download_range,  # this function will work in the background\n            args = (url, start, end, output_file),\n        )\n        process.start()\n        processes.append(process)\n\n    for process in processes:\n        process.join()  # wait for each process to complete\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Process_Substitution\"><\/span>Process Substitution<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A separate topic worth mentioning is process substitution in Bash via the <code>&lt;(...)<\/code> construct, since not everyone knows about it, but it makes life much easier. Sometimes you need to pass streams of information from other processes to commands, but the commands themselves can only accept file paths as input. You could redirect the output of processes to temporary files, but such code would be cumbersome. Therefore, Bash has support for process substitution. Essentially, a virtual file is created in the <code>\/dev\/fd\/<\/code> space, through which information is transmitted by passing the name of this file to the necessary command as a regular argument.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Find common processes on two hosts:\ncomm \\\n        &lt;(ssh user1@host1 'ps -x --format cmd' | sort) \\\n        &lt;(ssh user2@host2 'ps -x --format cmd' | sort)\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">from subprocess import check_output\n\ndef get_common_lines(lines1, lines2):\n    i, j = 0, 0\n    common = ()\n    while i &lt; len(lines1) and j &lt; len(lines2):\n        while lines2(j) &lt; lines1(i):\n            j += 1\n            if j &gt;= len(lines2):\n              return common\n        while lines2(j) &gt; lines1(i):\n            i += 1\n            if i &gt;= len(lines1):\n              return common\n        common.append(lines1(i))\n        i += 1\n        j += 1\n    return common\n\nlines1 = check_output(\n    ('ssh', 'user1@host1', 'ps -x --format cmd'),\n    text = True,\n).splitlines()\nlines1.sort()\n\nlines2 = check_output(\n    ('ssh', 'user2@host2', 'ps -x --format cmd'),\n    text = True,\n).splitlines()\nlines2.sort()\n\nprint(*get_common_lines(lines1, lines2), sep='\\n')\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Environment_Variables\"><\/span>Environment Variables<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"Working_with_Environment_Variables\"><\/span>Working with Environment Variables<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Environment variables allow passing information from parent processes to child processes. Bash has built-in support for environment variables at the language level, but there is no associative array of all environment variables. Information about them can only be obtained via the external <code>env<\/code> command.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Assigning a value to an environment variable:\nexport SOME_ENV_VAR='Some value'\n\necho \"${SOME_ENV_VAR}\"  # getting the value\n\nenv  # output the list of environment variables using an external command\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import os\n\n# Assigning a value to an environment variable:\nos.environ('SOME_ENV_VAR') = 'Some value'\n\nprint(os.environ('SOME_ENV_VAR'))  # getting the value\n\nprint(os.environ)  # output the array of environment variables\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Setting_Values_for_Individual_Processes\"><\/span>Setting Values for Individual Processes<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Environment variables are passed from the parent process to child processes. Sometimes you may need to change only one environment variable. Since Python is positioned as an application programming language, it will be somewhat more complicated to do this in Python, while in Bash, support for such variable setting is built-in:<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\"># Set Russian localization for launched applications\nexport LANG='ru_RU.UTF-8'\n\nLANG='C' ls --help  # but run this command with English localization\n\necho \"LANG=${LANG}\"  # make sure the environment variables are not affected\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import os\nimport subprocess\n\n# Assigning a value to an environment variable:\nos.environ('LANG') = 'ru_RU.UTF-8'\n\nnew_env = os.environ.copy()\nnew_env('LANG') = 'C'# Assigning a value to an environment variable:\nexport SOME_ENV_VAR='Some value'\n\necho \"${SOME_ENV_VAR}\" # getting the value\nsubprocess.run(\n    ('ls', '--help'),\n    env = new_env,\n)\n\nprint('LANG=' + os.environ('LANG'))  # make sure the environment variables are not affected\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Executing_Arbitrary_Code\"><\/span>Executing Arbitrary Code<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Executing arbitrary code is not required in everyday situations, but both languages have this capability. In Bash, this may be useful, for example, to return variables modified by the process or to return named execution results. In Python, there are two operators: <code>eval()<\/code> and <code>exec()<\/code>. The Bash <code>eval<\/code> analog in this case is the <code>exec()<\/code> operator, since it allows executing a list of commands, not just evaluating expressions. Using <code>eval()<\/code> and <code>exec()<\/code> is very bad practice in Python, and these operators can always be replaced with something more suitable, unless you need to write your own command interpreter based on Python.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">get_user_info()\n{\n    echo \"user=`whoami`\"\n    echo \"curr_dir=`pwd`\"\n}\neval $(get_user_info)  # execute the command output\necho \"${user}\"\necho \"${curr_dir}\"\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import getpass\nimport os\n\ndef user_info_code():\n    return f\"\"\"\nuser=\"{getpass.getuser()}\"  # very bad practice\ncurr_dir=\"{os.getcwd()}\"  # please don't do this\n\"\"\"\n\nexec(user_info_code())\nprint(user)\nprint(curr_dir)\n# But returning named values in general\n# is better through classes, namedtuple, or dictionaries\nfrom collections import namedtuple\nimport getpass\nimport os\n\nUserInfo=namedtuple('UserInfo', ('user', 'curr_dir'))\ndef get_user_info():\n    return UserInfo(getpass.getuser(), os.getcwd())\n\ninfo = get_user_info()\nprint(info.user)\nprint(info.curr_dir)\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Working_with_the_File_System_and_Processes\"><\/span>Working with the File System and Processes<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<h4><span class=\"ez-toc-section\" id=\"Getting_and_Changing_the_Current_Directory\"><\/span>Getting and Changing the Current Directory<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Changing the current directory in the command line is usually required when doing something manually. But getting the current directory may be needed in scripts, for example, if the script or the program being launched does something with files in the current directory. For the same reason, you may need to change the current directory if you need to run another program that does something in it.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">current_dir=`pwd`  # get the current directory\necho \"${current_dir}\"\n\ncd \/some\/path  # change to a directory\n<\/code><\/pre>\n<p>In Python:<\/p>\n<pre><code class=\"language-python\">import os\n\ncurrent_dir = os.getcwd()  # get the current directory\nprint(current_dir)\n\nos.chdir('\/some\/path')  # change to a directory\n<\/code><\/pre>\n<h4><span class=\"ez-toc-section\" id=\"Working_with_Signals\"><\/span>Working with Signals<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>In Bash, the <code>kill<\/code> command is built-in, which is why <code>man kill<\/code> will display help for a completely different command with different arguments. By the way, <code>sudo kill<\/code> will already call the <code>kill<\/code> utility. But Python code is still slightly clearer.<\/p>\n<p>In Bash:<\/p>\n<pre><code class=\"language-bash\">usr1_handler()\n{\n    echo \"Received USR1 signal\"\n}\n\n# Set a handler for the SIGUSR1 signal:\ntrap 'usr1_handler' USR1\n\n# Send a signal to the current interpreter:\nkill -USR1 $$  # $$ \u2014 PID of the parent interpreter\n<\/code><\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Compilation_Capability\"><\/span>Compilation Capability<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Bash by definition does not support compiling its scripts, which is perhaps why everything in it strives for minimalism in names. Python, although interpreted, can be compiled into platform-independent bytecode executed by the Python Virtual Machine (PVM). Executing such code can improve script performance. Usually, bytecode files have the <code>.pyc<\/code> extension.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Choosing_a_Language_Depending_on_the_Task\"><\/span>Choosing a Language Depending on the Task<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>As a summary of the article, the main postulates can be formed about which language is better to use in which cases.<\/p>\n<p>Bash is more advantageous to use in cases:<\/p>\n<ul>\n<li>solving simple tasks that can be solved faster with good knowledge of the language;<\/li>\n<li>simple command-line scripts where work is done with processes, files, directories, or even hard drives and the file system;<\/li>\n<li>if wrappers are created over other commands (starting a command interpreter can be faster than starting the Python interpreter);<\/li>\n<li>if Python is not available in the system for some reason.<\/li>\n<\/ul>\n<p>Python is more suitable for cases:<\/p>\n<ul>\n<li>solving tasks related to text processing, mathematical calculations, or implementing non-trivial algorithms;<\/li>\n<li>if Bash code would be difficult to read and understand;<\/li>\n<li>if you need to cover the code with unit tests (the <code>unittest<\/code> module);<\/li>\n<li>if you need to parse a large set of command-line parameters with a hierarchy of options between commands;<\/li>\n<li>if you need to display graphical dialog boxes;<\/li>\n<li>if script performance is critical (starting in Python may be slower, but executing code can be faster);<\/li>\n<li>for creating constantly running services (systemd services).<\/li>\n<\/ul><\/div>\n<p>In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.<\/p>\n<p><br \/>\n<br \/><a href=\"https:\/\/techplanet.today\/post\/comparing-bash-and-python-for-linux-scripting\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sh (from English shell) is a mandatory command interpreter for UNIX-compatible systems according to the POSIX standard. However, its capabilities are limited, so more feature-rich command interpreters such as Bash or Ksh are often used instead. Ksh is typically used in BSD-family operating systems, while Bash is used in Linux-family operating systems. Command interpreters simplify [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1466,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[3],"tags":[2857,2856,2858,88,2859],"class_list":["post-1465","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technologijos","tag-bash","tag-comparing","tag-linux","tag-python","tag-scripting"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/posts\/1465","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/comments?post=1465"}],"version-history":[{"count":0,"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/posts\/1465\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/media\/1466"}],"wp:attachment":[{"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/media?parent=1465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/categories?post=1465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/infonaujiena.lt\/index.php\/wp-json\/wp\/v2\/tags?post=1465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}