Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
scripting:newbie_traps [2011/06/20 21:30]
thebonsai [Expanding (using) variables]
scripting:newbie_traps [2016/05/11 15:19] (current)
stephenwb Added section about return value vs. output
Line 1: Line 1:
-====== ​Traps for beginners ​======+====== ​Beginner Mistakes ​======
  
 {{keywords>​bash shell scripting pitfalls traps beginners}} {{keywords>​bash shell scripting pitfalls traps beginners}}
  
-Beginners tend to not think when they type, even if they know the syntax and grammar rules. Some people also mix up different programming languages (like PHP) with Bash. That's not a big deal, that's why they are "​beginners"​. +Here are some typical traps:
- +
-Here are some typical traps, maybe the one you just stumbled in is in the list.+
  
 ===== Script execution ===== ===== Script execution =====
Line 13: Line 11:
 If you write Bash scripts with Bash specific syntax and features, run them with __Bash__, and run them with Bash in __native mode__. If you write Bash scripts with Bash specific syntax and features, run them with __Bash__, and run them with Bash in __native mode__.
  
-**Wrong** ​in this case:+**Wrong**:
   * no shebang   * no shebang
-    * depends on the OS implementation and current shell what will be used to interpret +    * the interpreter used depends on the OS implementation and current shell 
-    * of course you can always ​run it using the correct program: ​''​bash myscript''​+    * **can** be run by calling bash with the script name as an argument, e.g. ''​bash myscript''​
   * ''#​!/​bin/​sh''​ shebang   * ''#​!/​bin/​sh''​ shebang
-    * depends on what ''/​bin/​sh''​ actually is, for a Bash it means compatiblity mode, **not** ​the native mode+    * depends on what ''/​bin/​sh''​ actually is, for a Bash it means compatiblity mode, **not** native mode
  
 See also: See also:
Line 26: Line 24:
 ==== Your script named "​test"​ doesn'​t execute ==== ==== Your script named "​test"​ doesn'​t execute ====
  
-Give it another name. The executable ''​test'' ​does already ​exist.+Give it another name. The executable ''​test''​ already ​exists.
  
-In Bash it's builtin, on other shell it might be ran as executable file - in any way: Bad name choice.+In Bash it'​s ​builtin. With other shells, ​it might be an executable file. Either ​way, it's bad name choice!
  
 Workaround: You can call it using the pathname: Workaround: You can call it using the pathname:
Line 37: Line 35:
 ===== Globbing ===== ===== Globbing =====
  
-==== Brace expansion is no globbing ====+==== Brace expansion is not globbing ====
  
 The following command line is not related to globbing (filename expansion): The following command line is not related to globbing (filename expansion):
Line 49: Line 47:
 # -i*.vob -i # -i*.vob -i
 </​code>​ </​code>​
-**Why?** The brace expansion is simple text substitution. All possible ​texts formed by the prefix, the postfix and the braces themselves are generated. In the example, these are only two: ''​-i*.vob''​ and ''​-i''​. The filename expansion happens **after** that, so there is a chance that ''​-i*.vob''​ is expanded to a filename - if you have files like ''​-ihello.vob''​. But it definitely doesn'​t do what you thought it does.+**Why?** The brace expansion is simple text substitution. All possible ​text formed by the prefix, the postfix and the braces themselves are generated. In the example, these are only two: ''​-i*.vob''​ and ''​-i''​. The filename expansion happens **after** that, so there is a chance that ''​-i*.vob''​ is expanded to a filename - if you have files like ''​-ihello.vob''​. But it definitely doesn'​t do what you expected.
  
 Please see: Please see:
Line 69: Line 67:
 === The Dollar-Sign === === The Dollar-Sign ===
  
-There is no ''​$''​ (dollar-sign) when you reference the **name** of a variable! Bash is not PHP...+There is no ''​$''​ (dollar-sign) when you reference the **name** of a variable! Bash is not PHP!
 <​code>​ <​code>​
 # THIS IS WRONG! # THIS IS WRONG!
Line 75: Line 73:
 </​code>​ </​code>​
  
-A variable name preceeded with a dollar-sign always means that this variable gets **expanded**. In the case above, it might expand to nothing (because it wasn't set before), effectively resulting in...+A variable name preceeded with a dollar-sign always means that the variable gets **expanded**. In the example ​above, it might expand to nothing (because it wasn't set), effectively resulting in...
 <​code>​ <​code>​
 ="​Hello world!"​ ="​Hello world!"​
Line 81: Line 79:
 ...which **definitely is wrong**! ...which **definitely is wrong**!
  
-Everywhere ​you need the **name** of a variable, you write **only the name**, for example+When you need the **name** of a variable, you write **only the name**, for example
   * (as shown above) to set variables: ''​picture=/​usr/​share/​images/​foo.png''​   * (as shown above) to set variables: ''​picture=/​usr/​share/​images/​foo.png''​
   * to name variables to be used by the ''​read''​ builtin command: ''​read picture''​   * to name variables to be used by the ''​read''​ builtin command: ''​read picture''​
   * to name variables to be unset: ''​unset picture''​   * to name variables to be unset: ''​unset picture''​
  
-Everywhere ​you need the **content** of a variable, you preceed ​its name with **a dollar-sign**,​ like+When you need the **content** of a variable, you prefix ​its name with **a dollar-sign**,​ like
   * echo "The used picture is: $picture"​   * echo "The used picture is: $picture"​
  
-=== The Spaces ​===+=== Whitespace ​===
  
-Another thing which **will** fail is putting any spaces ​around ​the equal-sign (''​=''​) ​that's used to assign ​a value to a variable.+Putting ​spaces ​on either or both sides of the equal-sign (''​=''​) ​when assigning ​a value to a variable ​**will** fail.
  
 <​code>​ <​code>​
Line 104: Line 102:
 </​code>​ </​code>​
  
-The one and only valid form is to have **no spaces between the variable name and the value to be assigned**:+The only valid form is **no spaces between the variable name and assigned ​value**:
 <​code>​ <​code>​
 # CORRECT 1 # CORRECT 1
Line 115: Line 113:
 ==== Expanding (using) variables ==== ==== Expanding (using) variables ====
  
-A typical trap for beginners ​is to find the correct ​quoting ​method (or to quote at all).+A typical ​beginner'​s ​trap is quoting.
  
-As noted above, when you want to **expand** a variable ​("get the content"​), the variable name needs to be preceeded by a dollar-sign. But, since Bash knows various ways to quote and does word-splitting,​ the result isn't always the same.+As noted above, when you want to **expand** a variable ​i.e. "get the content",​ the variable name needs to be prefixed with a dollar-sign. But, since Bash knows various ways to quote and does word-splitting,​ the result isn't always the same.
  
 Let's define an example variable containing text with spaces: Let's define an example variable containing text with spaces:
Line 130: Line 128:
 |''<​nowiki>'​$example'</​nowiki>''​ |''​$example''​|1| |''<​nowiki>'​$example'</​nowiki>''​ |''​$example''​|1|
  
-But, if you use several forms of the parameter expansion, you **must** use the **name** (''​PATH''​) of the referenced variables/​parameters. ​You **must not** try to use their expansion ​(''​$PATH''​):​+If you use parameter expansion, you **must** use the **name** (''​PATH''​) of the referenced variables/​parameters. ​i.e. **not** (''​$PATH''​):​
 <​code>​ <​code>​
-# WRONG!!!+# WRONG!
 echo "The first character of PATH is ${$PATH:​0:​1}"​ echo "The first character of PATH is ${$PATH:​0:​1}"​
  
-# CORRECT!!!+# CORRECT
 echo "The first character of PATH is ${PATH:​0:​1}"​ echo "The first character of PATH is ${PATH:​0:​1}"​
 </​code>​ </​code>​
Line 157: Line 155:
 ==== Exporting ==== ==== Exporting ====
  
-Exporting a variable means to give **newly created** (child-)processes a copy of that variable. ​It'​s ​**not meant** to "copy back" ​a variable created in a child process to the parent. The following example does **not** work, since the variable ''​HELLO''​ is set in a child process (the process you execute to start that script ''​./​script.sh''​):​+Exporting a variable means to give **newly created** (child-)processes a copy of that variable. **not** copy a variable created in a child process to the parent ​process. The following example does **not** work, since the variable ''​hello''​ is set in a child process (the process you execute to start that script ''​./​script.sh''​):​
  
 <​code>​ <​code>​
 $ cat script.sh $ cat script.sh
-export ​hellp=world+export ​hello=world
  
 $ ./script.sh $ ./script.sh
-$ echo $hellp+$ echo $hello
 $ $
 </​code>​ </​code>​
  
-Exporting is one-way. The direction is parent process ​=> child process, ​and not vice versa. The above example **will** work, when you don't execute the script, but include ("​source"​) it:+Exporting is one-way. The direction is parent process ​to child process, not the reverse. The above example **will** work, when you don't execute the script, but include ("​source"​) it:
 <​code>​ <​code>​
 $ source ./script.sh $ source ./script.sh
-$ echo $hellp+$ echo $hello
 world world
 $ $
 </​code>​ </​code>​
-...but then the ''​export'' ​command is useless, at least in this case.+In this case, the export command is of no use.
  
 Please see: Please see:
Line 181: Line 179:
  
 ===== Exit codes ===== ===== Exit codes =====
-==== Reacting ​on exit codes ====+==== Reacting ​to exit codes ====
  
-If you just want to react on an exit code, disregarding the specific value of the exit code, you **don'​t need** to bother with ''​$?''​ in a test command like this:+If you just want to react to an exit code, regardless of its specific value, you **don'​t need** to use ''​$?''​ in a test command like this:
  
 <code bash> <code bash>
Line 193: Line 191:
 </​code>​ </​code>​
  
-This code can be simplified:+This can be simplified ​to:
 <code bash> <code bash>
 if ! grep ^root: /etc/passwd >/​dev/​null 2>&​1;​ then if ! grep ^root: /etc/passwd >/​dev/​null 2>&​1;​ then
Line 200: Line 198:
 </​code>​ </​code>​
  
-If you need the specific value of ''​$?'',​ there'​s no other chance, yes. But if you're just interested in conditional code based on "​true/​false"​ exit code meaning, there'​s no need to bother with ''​$?'' ​directly.+Or, simpler yet: 
 +<code bash> 
 +grep ^root: /etc/passwd >/​dev/​null 2>&1 || echo "root was not found - check the pub at the corner"​ 
 +</​code>​ 
 + 
 +If you need the specific value of ''​$?'',​ there'​s no other choice. But if you need only a "​true/​false"​ exit indication, there'​s no need for ''​$?''​.
  
 See also: See also:
   * [[scripting:​basics#​exit_codes | Exit codes]]   * [[scripting:​basics#​exit_codes | Exit codes]]
 +
 +==== Output vs. Return Value ====
 +
 +It's important to remember the different ways to run a child command, and whether you want the output, the return value, or neither.
 +
 +When you want to run a command (or a pipeline) and save (or print) the **output**, whether as a string or an array, you use Bash's ''​$(command)''​ syntax:
 +<​code>​
 +$(ls -l /tmp)
 +newvariable=$(printf "​foo"​)
 +</​code>​
 +
 +When you want to use the **return value** of a command, just use the command, or add ( ) to run a command or pipeline in a subshell:
 +<​code>​
 +if grep someuser /etc/passwd ; then
 +    # do something
 +fi
 +
 +if ( w | grep someuser | grep sqlplus ) ; then
 +    # someuser is logged in and running sqlplus
 +fi
 +</​code>​
 +
 +Make sure you're using the form you intended:
 +<​code>​
 +# WRONG!
 +if $(grep ERROR /​var/​log/​messages) ; then
 +    # send alerts
 +fi
 +</​code>​
 +
 +Please see:
 +  * [[syntax:​ccmd:​intro]]
 +  * [[syntax:​expansion:​cmdsubst]]
 +  * [[syntax:​ccmd:​grouping_subshell]]