📜 ⬆️ ⬇️

Pitfalls Bash



In this article we’ll talk about errors made by bash programmers. In all the examples there are some flaws. You will be able to avoid many of the errors described below if you always use quotes and never use wordsplitting! Word breaking is a flawed legacy practice inherited from the Bourne shell. It is applied by default if you do not enclose quotes. In general, the overwhelming majority of pitfalls are somehow connected with the substitution without quotation marks, which leads to word splitting and globbing of the resulting result.



1. for i in $ (ls * .mp3)


One of the most common mistakes made by BASH-programmers. It is expressed in the writing of such cycles:
')
for i in $(ls *.mp3); do # ! some command $i # ! done for i in $(ls) # ! for i in `ls` # ! for i in $(find . -type f) # ! for i in `find . -type f` # ! files=($(find . -type f)) # ! for i in ${files[@]} # ! 

Yes, it would be great if you could process the output ls or find as a list of file names and iterate it. But you can not . This approach is entirely wrong, and this can not be corrected. You need to approach this completely differently.

There are at least five problems here:

  1. If the file name contains spaces, then it is subject to WordSplitting . Suppose that in the current folder we have a file named 01 - Don't Eat the Yellow Snow.mp3 . The for loop iterates each word and returns the result: 01, -, Don't, Eat , etc.

  2. If the file name contains glob characters, then it is subjected to globbing (" globbing "). If the output of ls contains the character * , then the word into which it belongs will be regarded as a template and replaced with a list of all the file names that correspond to it. The path to the file can contain any characters, with the exception of NUL. Yes, including newline characters.

  3. The ls utility can shred file names. Depending on the platform on which you are working out, on the arguments (or not) you use, and also on whether the standard output is pointing to the terminal, ls can suddenly replace some characters in the file name with a "?". Or even do not display them. Never attempt to parse the output ls .

  4. The CommandSubstitution truncates all end line feeds from the output. At first glance, this is good, because ls adds a new line. But if the last file name in the list ends with a new line, then `...` or $() will also be removed in addition.

You also can not enclose the substitution in double quotes:

 for i in "$(ls *.mp3)"; do # ! 

This will result in the entire output of ls being considered as one word. Instead of iterating each file name, the loop will be executed once , assigning i string value from the combined file names. And you can't just change the IFS to a new line. File names may also contain new lines.

Another variation on this topic is the abuse of word splitting and for loops for (incorrect) reading lines of a file. For example:

 IFS=$'\n' for line in $(cat file); do ... # ! 

This does not work! Especially if the strings are file names. Bash (like any other Bourne shell) just doesn't work that way. In addition to all the above, you absolutely do not need to use ls itself. This is an external command, the output of which is specifically designed for reading by a person, and not for parsing by a script.

So how do you do it right?

Use find , for example, in conjunction with -exec :

 find . -type f -exec some command {} \; 

Instead of ls you can consider this option:

 for i in *.mp3; do #  ! ... some command "$i" # ...    ! done 

POSIX shells, like Bash, have the globbing property for this purpose — this allows them to apply templates to the list of matching file names. No need to interpret the results of the external utility. Since globbing is the last stage of the substitution procedure, the *.mp3 pattern is correctly applied to individual words that are not affected by the wildcard substitution. If you need to recursively process files, use UsingFind or take a look at shopt -s globstar in Bash 4 and higher.

Question: What happens if there are no files in the current folder that satisfy the pattern * .mp3? The for loop will be executed once with i="*.mp3" , which is not the expected behavior! As a solution to this problem, you can check for the presence of a suitable file:

 # POSIX for i in *.mp3; do [ -e "$i" ] || continue some command "$i" done 

Another solution is to use the Bash shopt -s nullglob . Although this can be done only after reading the documentation and carefully evaluating the effect of this setting on all other globs in this script. Note the quotes around $i in the body of the loop. This leads us to the second problem:

2. cp $ file $ target


What is wrong with this team? In principle, nothing if you know in advance that $file and $target do not contain spaces or wildcards. However, the results of the substitution still undergo WordSplitting and file path substitution. Therefore, always include parameter expansions in double quotes.

 cp -- "$file" "$target" 

Otherwise, you will receive the following command cp 01 - Don't Eat the Yellow Snow.mp3 /mnt/usb , which will lead to errors like cp: cannot stat `01': No such file or directory . If $file contains wildcard characters ( * or ? Or [ ), then they will be expanded only if there are matching files. With double quotes, everything will be fine, until at the beginning of "$ file" there is a "-" character. In this case, cp will decide that you are trying to feed command line options to it (see the next chapter).

Even in a few unusual circumstances, when you can guarantee the contents of a variable, quoting parameter substitution is a good and common practice, especially if they contain filenames. Experienced script writers will always use quotes, except in rare cases when it is absolutely clear from the context of the code that the parameter contains a guaranteed safe value. Experts will certainly decide that using the cp command in the header is an error. You should think so too.

3. Filenames with preceding hyphens


File names with preceding hyphens can cause a lot of problems. Globs like *.mp3 sorted into an expanded list (according to your current locale), and in most locales a hyphen is sorted first and then letters. Then the list is passed to some command that may incorrectly interpret -filename as an option. This situation has two main solutions.

The first is to insert two hyphens ( -- ) between the command (for example, cp ) and its arguments. This will be a signal to stop searching for options, and everything will be fine:

 cp -- "$file" "$target" 

But this approach has its own problems. You must be sure to insert -- every time you use a parameter in context, when it can be interpreted as an option. This implies greater redundancy, and you can easily miss something.

Most of the well-written parsing options libraries understand this, and programs that correctly use them should inherit this feature for free. However, keep in mind that the responsibility for recognizing the end of options rests solely with the application. Some programs that parse options manually, or do it incorrectly, or use third-party libraries may not recognize the endings. Standard utilities should do this, apart from the few exceptions described in POSIX. For example, echo .

Another option is to make sure that your file names always begin with a folder. To do this, use relative or absolute file paths.

 for i in ./*.mp3; do cp "$i" /target ... done 

In this case, even if we have a file whose name begins with a hyphen, thanks to glob we can be sure that the variable always contains something like ./-foo.mp3 . And it is perfectly safe, if we talk about cp .

Finally, if you can guarantee that all the results will have the same prefix, and the variable is used in the body of the loop only a few times, you can simply concatenate the prefix during the substitution. Theoretically, it will save us the generation and storage of several additional characters for each word.

 for i in *.mp3; do cp "./$i" /target ... done 

4. [$ foo = "bar"]


This situation is very similar to the problem described in the second chapter. But still, I will repeat it, because it is very important. In the header line quoted, the quotes are not where they are needed. In Bash, you do not need to quote quotes for string literals (unless they contain metacharacters or wildcards). But you must quote your variables if they can contain spaces or wildcard characters.

The above example can break for several reasons:


Bash and many other ksh-like shells have an excellent alternative using the keyword [[ .

 # Bash / Ksh [[ $foo == bar ]] # ! 

You do not need to quote links to variables to the left of = inside [[ ]] , because they are not subject to word splitting or globbing. And even empty variables will be processed correctly. On the other hand, the use of quotes does not hurt. Unlike [ and test , you can also use == . Just note that when comparing using [[ pattern search is performed for the strings on the right side, not a simple string comparison. To make the right string literal, you must put it in quotes when using any characters that have special meaning in the context of a pattern search.

 # Bash / Ksh match=b*r [[ $foo == "$match" ]] # !    ,       b*r. 

You've probably seen similar code:

 # POSIX / Bourne [ x"$foo" = xbar ] # ,    . 

For code that runs on very ancient shells, the hack x " $foo " will be required. Here, instead of [[ more primitive is used [ . If $foo starts with a hyphen, then confusion arises. On older systems [ does not care about whether the token to the right of = begins with a hyphen. She uses it literally. So you need to be more careful with the left side.

Note that shells that need this workaround are not compatible with POSIX. Even Heirloom Bourne does not require this (it is probably a non-POSIX clone of the Bourne shell, which is still one of the most common system shells). Such extreme portability is rarely required, it makes your code less readable and beautiful.

5. cd $ (dirname "$ f")


Another error related to quotes. As is the case with variable expansion (variable expansion), the result of command substitution is subjected to word splitting and file path substitution. Therefore, enclose in quotes:

 cd -P -- "$(dirname -- "$f")" 

The logic of quotation is not completely obvious here. A C programmer will expect that the first and second double quotes will be grouped together, and then the third and fourth quotes will be displayed. But in Bash everything is different. Bash treats double quotes inside the command substitution as one pair, and double quotes outside the substitution like the other pair.

You can write differently: the parser treats the command substitution as “nesting level”, and the quotes inside go separately from the quotes outside.

6. ["$ foo" = bar && "$ bar" = foo]


You cannot use && inside the old test (or [) command. The Bash parser sees && outside [[ ]] or (( )) , and as a result splits your command into two commands — before and after && . Instead, use one of two options:

 [ bar = "$foo" ] && [ foo = "$bar" ] # ! (POSIX) [[ $foo = bar && $bar = foo ]] #  ! (Bash / Ksh) 

(Note that due to the Legacy mentioned in Chapter 4, we swapped the constant and variable inside [ . We could also change [[ , but to prevent interpretation, we would have to take substitutions in quotes).

The same applies to || . Instead, use [[ or two commands [ .

Avoid this:

 [ bar = "$foo" -a foo = "$bar" ] #  . 

The binary -a , -o and (/) (grouping) operators are POSIX XSI extensions. In POSIX 2008, they are all marked as obsolete. Use them in the new code is not worth it. One of the practical problems associated with [ A = B -a C = D ] (or -o ) is that POSIX does not define the results of the test commands or [ with more than four arguments. This will probably work in most shells, but you cannot count on it. If you need to write for the POSIX shell, then use two test commands or [ , separated by the && operator.

7. [[$ foo> 7]]


There are a few points here. First, the command [[ should not be used exclusively to calculate arithmetic expressions. It should be used for test expressions that include one of the supported test operators. Although technically you can perform calculations with the help of operators [[ , but it makes sense only in combination with one of the non-mathematical test operators, which is present somewhere in the expression. If you just want to compare numeric data (or perform any other arithmetic operation), then it is much better to use (( )) :

 # Bash / Ksh ((foo > 7)) # ! [[ foo -gt 7 ]] # ,  .   .   ((...))  let. 

If inside [[ ]] you use the> operator, then the system treats it as a comparison of string data (checking the sort order by locale), not numeric. Sometimes it can work, but it will let you down just when you least expect it. It is even worse to use> inside [ ] : this is a redirection of the output. A file with the name 7 will appear in your folder, and the test will run successfully until there is something in $foo .

If strict compatibility with POSIX is required and the command is not available (( , then using the old-fashioned [ :

 # POSIX [ "$foo" -gt 7 ] #  ! [ $((foo > 7)) -ne 0 ] #   POSIX  ((     . 

Note that if $foo not integer, then the test ... -gt will fail. Therefore, quotation makes sense only for the sake of performance and separation of arguments into single words in order to reduce the likelihood of side effects in some shells.

If you cannot guarantee the input data for any arithmetic context (including (( or let )) or a test expression [ that implies numeric comparisons, then you should always validate the input data before performing the calculation.

 # POSIX case $foo in *[![:digit:]]*) printf '$foo expanded to a non-digit: %s\n' "$foo" >&2 exit 1 ;; *) [ $foo -gt 7 ] esac 

8. grep foo bar | while read -r; do ((count ++)); done


Does this code look ok? Of course, this is just a mediocre implementation of grep -c , but this is done for simplicity of example. Changes in count will not propagate beyond the boundaries of the while , because each pipeline command is executed in a separate subshell ( SubShell ). At some point, this surprises any newcomer to Bash.

POSIX does not determine whether the last element of the pipeline should be calculated in a subshell. Some shells, like ksh93 and Bash> = 4.2 with shopt -s lastpipe , will run the example while in the original shell process, which can lead to any side effects. Therefore, portable scripts should be written in such a way that they do not depend on such behavior.

Ways to solve this and similar problems you can learn from the Bash FAQ # 24 . It is too long to describe them here.

9. if [grep foo myfile]


Many beginners have an erroneous idea of if expressions, due to the fact that very often this keyword immediately follows [ or [[ . People believe that [ somehow part of the if expression syntax, as are the simple brackets used in the C expression in the C language. This is not so! if receives command . It is [ , it is not a syntax marker for if . This command is equivalent to test , except that the last argument must be ] . For example:

 # POSIX if [ false ]; then echo "HELP"; fi if test false; then echo "HELP"; fi 

These lines are equivalent: both check that the argument “false” is not empty. In both cases, HELP will be output, to the surprise of programmers who have come from other languages ​​and are trying to figure out the shell syntax.

The if expression has the following syntax:

 if COMMANDS then <COMMANDS> elif <COMMANDS> # optional then <COMMANDS> else <COMMANDS> # optional fi # required 

Once again - [ is a command. She gets arguments like any other regular command. if is a compound command containing other commands. And in its syntax is not [ !

Although Bash has a builtin command [ , and so it knows about [ , c ] there is nothing special. Bash only passes ] as an argument to the command [ , which needs it to be ] last argument, otherwise the script will look ugly.

There may be zero and more optional elif sections, as well as one optional else section.

The compound if command contains two or more sections that contain lists of commands. Each section begins with the keyword then , elif or else , and ends with the keyword fi . The completion code of the last command of the first section and each subsequent section of elif determine the calculation of each corresponding section of then . The other section of elif computed before one of the then is executed. If no then section is calculated, then switching to the else branch. If there is no else , the if block if completed, and the resulting if command returns 0 (true).

If you want to make a decision depending on the output of the grep , then you do not need to enclose it in parentheses or square brackets, backticks, or any other syntax! Just use grep as a command after the if :

 if grep -q fooregex myfile; then ... fi 

If grep finds a match in the string from myfile , then the myfile code will be 0 (true), and the then part will be executed. If no match is found, grep will return a value other than 0, and the resulting if command will be zero.

See also:


10. if [bar = "$ foo"]; then ...


 [bar="$foo"] # ! [ bar="$foo" ] #   ! 

As explained in the previous chapter, [ is a command (this can be proved with type -t [ or whence -v [ ). As with any other simple command, Bash expects a space after it, then the first argument, space again, and so on. You just can not neglect the spaces! Here is the correct spelling:

 if [ bar = "$foo" ]; then ... 

Each of the components - bar , = , the substitution of " $foo " and ] - are separate arguments to the command [ . , , .

11. if [ [ a = b ] && [ c = d ] ]; then ...


. [ . , if - «», . [ . - if Bash-, !

, :

 if [ a = b ] && [ c = d ]; then ... 

, if , && ( AND, ). , :

 if test a = b && test c = d; then ... 

test false, if . true, test ; true, if . (C- && . Bash . , || OR .)

[[ && , :

 if [[ a = b && c = d ]]; then ... 

6 , test .

12. read $foo


$ read . foo , :

 read foo 

:

 IFS= read -r foo 

read $foo / $foo . , foo ; .

13. cat file | sed s/foo/bar/ > file


. , , :


, , .

 printf %s\\n ',s/foo/bar/g' wq | ed -s file 

, (*) .

:

 sed 's/foo/bar/g' file > tmpfile && mv tmpfile file 

GNU sed 4.x:

 sed -i 's/foo/bar/g' file(s) 

, — .

- Perl 5.x (, , , GNU sed 4.x):

 perl -pi -e 's/foo/bar/g' file(s) 

Bash FAQ #21 .

(*) sponge moreutils :

 sed '...' file | grep '...' | sponge file 

mv , «» ( !) , file . , , , .

+ mv / . , mv sync .

14. echo $foo


. $foo , , . - Bash- , , . .

 msg=",    *.zip" echo $msg 

, (glob) ( *.zip) . , : , freenfss.zip lw35nfss.zip . :

 var="*.zip" # var  ,    "zip" echo "$var" #  *.zip echo $var #   ,   .zip 

, echo . , , -n , echo , , . — printf :

 printf "%s\n" "$foo" 

15. $foo=bar


, $ , . Perl.

16. foo = bar


, = , . . foo = bar , . — foo — . — .


17. echo <<EOF


Here- — . . , echo stdin.

 #  : echo <<EOF Hello world How's it going? EOF #     : cat <<EOF Hello world How's it going? EOF #   ,      (, echo ): echo "Hello world How's it going?" 

. . . , , cat , :

 #   printf ( , printf ): printf %s "\ Hello world How's it going? " 

printf , \ . ( ). \n printf . \ . , , «» :

 printf %s \ 'Hello world ' printf %s 'Hello world ' 

18. su -c 'some command'


. , su -c , , . OpenBSD:

 $ su -c 'echo hello' su: only the superuser may specify a login class 

-c 'some command' , -c .

 su root -c 'some command' # Now it's right. 

su root-, . , . .

19. cd /foo; bar


cd , bar . , , , bar rm -f * . cd . :

 cd /foo && bar 

cd , :

 cd /foo || exit 1 bar baz bat ... # Lots of commands. 

cd , stderr- «bash: cd: /foo: No such file or directory». stdout , :

 cd /net || { echo >&2 "Can't read /net. Make sure you've logged in to the Samba network, and try again."; xit 1; } do_stuff more_stuff 

, { echo . } ; .

set -e , , , . ( , ).

, Bash-, pushd , popd dirs . , , cd pwd , . :

 find ... -type d -print0 | while IFS= read -r -d '' subdir; do here=$PWD cd "$subdir" && whatever && ... cd "$here" done 

C :

 find ... -type d -print0 | while IFS= read -r -d '' subdir; do (cd "$subdir" || exit; whatever; ...) done 

cd . , , cd . , ... && ... , . ( ).

20. [ bar == "$foo" ]


== POSIX- [ . = [[ .

 [ bar = "$foo" ] && echo yes [[ bar == $foo ]] && echo yes 

Bash [ "$x" == y ] , . — «» ( Bashism ). , [[ .

21. for i in {1..10}; do ./something &; done


; & . ; .

  for i in {1..10}; do ./something & done : for i in {1..10}; do ./something & done 

& (command terminator), ; . .

, ; , ; .

22. cmd1 && cmd2 || cmd3


- && || if ... then ... else ... fi . :

 [[ -s $errorlog ]] && echo "Uh oh, there were some errors." || echo "Successful." 

if ... fi . , && , . «true» (0), , || . For example:

 i=0 true && ((i++)) || ((i--)) echo $i # Prints 0 

What's going on here? , i 1, 0. ? i++ , i-- . ((i++)) , . 0 ( i ), , 0, false . ((i++)) ( i 0) 1 (false), ((i--)) .

, , ++i true:

 i=0 true && (( ++i )) || (( --i )) echo $i # Prints 1 

. x && y || z , y ! , i -1 0.

, , - , , if ... fi .

 i=0 if true; then ((i++)) else ((i--)) fi echo $i # Prints 1 

Bourne:

 true && { echo true; false; } || { echo false; true; } 

«true» «false», «true».

23. echo «Hello World!»


Bash ( 4.3), :

 bash: !": event not found 

, , Bash (history expansion) csh, . , . , «» :

 $ echo "hi\!" hi\! 

histexpand . set +H set +o histexpand :

: histexpand , ?

,

 mp3info -t "Don't Let It Show" ... mp3info -t "Ah! Leah!" ... 

, . . , , . . , ~/.bashrc. -- GreyCat

:

 echo 'Hello World!' 

Or

 set +H echo "Hello World!" 

Or

 histchars= 

set +H set +o histexpand ~/.bashrc , . , , .

:

 exmark='!' echo "Hello, world$exmark" 

Bash 4.3 , ! . , echo "Hello World!" , :

 echo "Hello, World!(and the rest of the Universe)" echo "foo!'bar'" 

24. for arg in $*


Bash ( Bourne-) , . $* , $@ . , . :

 for arg in "$@" #  : for arg 

, for arg for arg in "$@" . "$@" — , ( ). 99% .

Example:

 #   for x in $*; do echo "parameter: '$x'" done $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg' parameter: '1' parameter: 'arg2' parameter: 'arg3' 

:

 #   for x in "$@"; do echo "parameter: '$x'" done #  : for x; do echo "parameter: '$x'" done $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg 1' parameter: 'arg2' parameter: 'arg3' 

25. function foo()


, . function () . Bash ( ) . (, zsh 4.x , , ). function foo , :

 foo() { ... } 

26. echo "~"


, '~' . echo stdout '~', . , , $HOME, '~'. , $HOME — "/home/my photos".

 "~/dir with spaces" #   "~/dir with spaces" ~"/dir with spaces" #   "~/dir with spaces" ~/"dir with spaces" #   "/home/my photos/dir with spaces" "$HOME/dir with spaces" #   "/home/my photos/dir with spaces" 

27. local varname=$(command)


, local . . , ( $? ) , . . :

 local varname varname=$(command) rc=$? 

.

28. export foo=~/bar


, — — ( ). , = .

export local . ( Bash) export foo=~/bar , ( dash) — .

 foo=~/bar; export foo # ! export foo="$HOME/bar" # ! 

29. sed 's/$foo/good bye/'


$foo . — $ . :

 foo="hello"; sed "s/$foo/good bye/" 

: escapes. « ».

30. tr [AZ] [az]


. : [AZ] [az] . , , . , . , 3 .

: tr . , '[' '[', - AZ az, ']' ']'. , .

, , AZ az 26 ASCII-. z ! , :

 # ,    26   LC_COLLATE=C tr AZ az # ,        .       tr '[:upper:]' '[:lower:]' 

, .

31. ps ax | grep gedit


, . gedit. - , gedit ( ). , . PID gedit (),

 $ ps ax | grep gedit 10530 ? S 6:23 gedit 32118 pts/0 R+ 0:00 grep gedit 

, Race Condition , grep. :

 ps ax | grep -v grep | grep gedit # ,    


:

 ps ax | grep '[g]edit' #   ,   shell GLOB 

Grep , [g]edit , grep gedit .

GNU/Linux –C :

 $ ps -C gedit PID TTY TIME CMD 10530 ? 00:06:23 gedit 

, pgrep ?

 $ pgrep gedit 10530 

PID awk cut :

 $ ps -C gedit | awk '{print $1}' | tail -n1 

ps :

 $ ps -C gedit -opid= 10530 

1992 pgrep , , pidof ( GNU/Linux):

 $ pidof gedit 10530 

PID, , pkill . , , , pgrep/pkill ssh sshd, .

, . , Firefox firefox-bin. , , ps ax | grep firefox . pgrep:

 $ pgrep -fl firefox 3128 /usr/lib/firefox/firefox 7120 /usr/lib/firefox/plugin-container /usr/lib/flashplugin-installer/libflashplayer.so -greomni /usr/lib/firefox/omni.ja 3128 true plugin 

. Seriously.

32. printf "$foo"


, . $foo , \ % . :

 printf %s "$foo" printf '%s\n' "$foo" 

33. for i in {1..$n}


Bash . $n , , . , runtime. :

 for ((i=1; i<=n; i++)); do ... done 

for , , (pre-expands), .

34. if [[ $foo = $bar ]] ( )


, = [[, Bash , . bar *, true. , :

 if [[ $foo = "$bar" ]] 

, , , . .

, =~ , , . .

35. if [[ $foo =~ 'some RE' ]]


, . , , :

 re='some RE' if [[ $foo =~ $re ]] 

=~ Bash. .

[[ :

 [[ $foo = "*.glob" ]] # Wrong! *.glob is treated as a literal string. [[ $foo = *.glob ]] # Correct. *.glob is treated as a glob-style pattern. 

36. [ -n $foo ] or [ -z $foo ]


[ , . $foo 0 , 42 , , 1, .

 [ -n "$foo" ] [ -z "$foo" ] [ -n "$( -   "$file")" ] # [[          ,      : [[ -n $foo ]] [[ -z $foo ]] 

37. [[ -e "$broken_symlink" ]] 1 $broken_symlink


Test symlink', , symlink , — , , , — test –e 1, symlink. ( ), :

 # bash/ksh/zsh [[ -e "$broken_symlink" || -L "$broken_symlink" ]] # POSIX sh+test [ -e "$broken_symlink" ] || [ -L "$broken_symlink" ] 

38. ed file <<<«g/d\{0,3\}/s//e/g»


, ed 0 \{0,3\}. , :

 ed file <<<"g/d\{1,3\}/s//e/g" 

, POSIX-, BRE ( , ed), 0 (. 5) .

39. (sub-string) expr «match»




 word=abcde expr "$word" : ".\(.*\)" bcde 

«match»

 word=match expr "$word" : ".\(.*\)" 

, «match» — . GNU '+'

 word=match expr + "$word" : ".\(.*\)" atch 

expr . , , ( Parameter Expansion ). , ? POSIX- (Substring Expansion):

 $ word=match $ echo "${word#?}" # PE atch $ echo "${word:1}" # SE atch 

, expr , Solaris POSIX /bin/sh . , , . , , , .

40. UTF-8 (Byte-Order Marks, BOM)


: Unix UTF-8 BOM. , MIME- . BOM UTF-8, , ( - ) , , , . , BOM, , , MS-DOS.

: , UTF-8 8- , BOM , , ASCII-, "#!" Unix-».

http://unicode.org/faq/utf_bom.html#bom5

41. content=$(<file)


, , ( : `...` , $(...) , $(<file) , `<file` ${ ...; } (ksh)) . , , , , , , . , : .

 absolute_dir_path_x=$(readlink -fn -- "$dir_path"; printf x) absolute_dir_path=${absolute_dir_path_x%x} 

, : read .

 # Ksh (or bash 4.2+ with lastpipe enabled) readlink -fn -- "$dir_path" | IFS= read -rd '' absolute_dir_path 

, read false, NUL-, . — PIPESTATUS . NUL-, read true, pipefail .

 set -o pipefail { readlink -fn -- "$dir_path"; printf '\0x'; } | IFS= read -rd '' absolute_dir_path 

: Bash pipefail PIPESTATUS , ksh93 — pipefail , mksh pipefail , — PIPESTATUS . , ksh93, read NUL-.

42. for file in ./*; do if [[ $file != *.* ]]


, (. 3). ./ .

*.* , ./filename . . , (, , ), : [[ $file != ./*.* ]] , .

 # Bash shopt -s nullglob for path in ./*; do [[ ${path##*/} != *.* ]] && rm "$path" done #    for file in *; do [[ $file != *.* ]] && rm "./${file}" done #    for file in *.*; do rm "./${file}" done 

-- ( 3).

 shopt -s nullglob for file in *; do [[ $file != *.* ]] && rm -- "$file" done 

43. somecmd 2>&1 >>logfile


, , , , stdout stderr. , stderr . , , . , . : « , (tty), -». . tty. :

 somecmd >>logfile 2>&1 

, Copy BashGuide — redirection .

44. cmd; ((! $? )) || die


$? , . , ( ), , :

 if cmd; then ... fi 

:

 cmd status=$? case $status in 0) echo success >&2 ;; 1) echo 'Must supply a parameter, exiting.' >&2 exit 1 ;; *) echo "Unknown error $status, exiting." >&2 exit "$status" esac 

45. y=$(( array[$x] ))


POSIX (arithmetic expansion) ( ), (array subscript) . , , .

 $ x='$(date >&2)' #    ,  ,   $ y=$((array[$x])) #      Mon Jun 2 10:49:08 EDT 2014 

:

 $ y=$((array["$x"])) Mon Jun 2 10:51:03 EDT 2014 

:

 # 1.  $x,       . $ y=$((array[\$x])) # 2.    ${array[$x]}. $ y=$((${array[$x]})) 

46. read num; echo $((num+1))


(. BashFAQ/054 ), num , .

 $ echo 'a[$(echo injection >&2)]' | bash -c 'read num; echo $((num+1))' injection 1 

47. IFS=, read -ra fields <<< "$csv_line"


, POSIX IFS (field terminator), . , , :

 $ IFS=, read -ra fields <<< "a,b," $ declare -p fields declare -a fields='([0]="a" [1]="b")' 

? (« »). Bash, . :

 $ IFS=, read -ra fields <<< "a,b,c" $ declare -p fields declare -a fields='([0]="a" [1]="b" [2]="c")' 

? , IFS- . , IFS- «» , . , IFS- , , .

 $ input="a,b," $ IFS=, read -ra fields <<< "$input," $ declare -p fields declare -a fields='([0]="a" [1]="b" [2]="")' 

48. export CDPATH=.:~/myProject


CDPATH. CDPATH .bashrc , , Bash- sh-, cd , . . , :

 cd some/dir || exit cmd to be run in some/dir 

./some/dir ~/myProject/some/dir , , . cd , , , .

cd , :

 output=$(cd some/dir && some command) 

CDPATH , cd stdout /home/user/some/dir , , CDPATH, some command .

CDPATH, , ./ . unset CDPATH , , , CDPATH.

Source: https://habr.com/ru/post/311762/


All Articles