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[@]} # !
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.01 - Don't Eat the Yellow Snow.mp3
. The for
loop iterates each word and returns the result: 01, -, Don't, Eat , etc.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.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 .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. for i in "$(ls *.mp3)"; do # !
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. IFS=$'\n' for line in $(cat file); do ... # !
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.find
, for example, in conjunction with -exec
: find . -type f -exec some command {} \;
ls
you can consider this option: for i in *.mp3; do # ! ... some command "$i" # ... ! done
*.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.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
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:$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"
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).cp
command in the header is an error. You should think so too.*.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.--
) 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"
--
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.echo
. for i in ./*.mp3; do cp "$i" /target ... done
./-foo.mp3
. And it is perfectly safe, if we talk about cp
. for i in *.mp3; do cp "./$i" /target ... done
[
does not exist or is empty, then the command [
will ultimately look like this: [ = "bar" ] # !
unary operator expected.
(Operator =
is binary, not unary, so the team [
will be shocked to see him).[
. Therefore, we get: [ multiple words here = "bar" ]
[
there is a syntax error. The correct way to write: # POSIX [ "$foo" = bar ] # !
$foo
, because in the POSIX command, [
determines its actions depending on the number of arguments passed to it. Only very ancient shells will have problems with this, you can not worry about them when writing code (see below the trick with x " $foo
"). # Bash / Ksh [[ $foo == bar ]] # !
=
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.
# POSIX / Bourne [ x"$foo" = xbar ] # , .
$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. cd -P -- "$(dirname -- "$f")"
&&
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)
[
. We could also change [[
, but to prevent interpretation, we would have to take substitutions in quotes).||
. Instead, use [[
or two commands [
. [ bar = "$foo" -a foo = "$bar" ] # .
-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.[[
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.
[[ ]]
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
.((
, then using the old-fashioned [
: # POSIX [ "$foo" -gt 7 ] # ! [ $((foo > 7)) -ne 0 ] # POSIX (( .
$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.((
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
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.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.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
if
expression has the following syntax: if COMMANDS then <COMMANDS> elif <COMMANDS> # optional then <COMMANDS> else <COMMANDS> # optional fi # required
[
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 [ ![
, 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.elif
sections, as well as one optional else
section.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).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
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. [bar="$foo"] # ! [ bar="$foo" ] # !
[
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 ...
bar
, =
, the substitution of " $foo
" and ]
- are separate arguments to the command [
. , , .[
. , 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 ...
test
.$
read
. foo
, : read foo
IFS= read -r foo
read $foo
/ $foo
. , foo
; . printf %s\\n ',s/foo/bar/g' wq | ed -s file
sed 's/foo/bar/g' file > tmpfile && mv tmpfile file
sed -i 's/foo/bar/g' file(s)
perl -pi -e 's/foo/bar/g' file(s)
sponge
moreutils : sed '...' file | grep '...' | sponge file
mv
, «» ( !) , file
. , , , .+ mv
/ . , mv
sync
.$foo
, , . - Bash- , , . . msg=", *.zip" echo $msg
, freenfss.zip lw35nfss.zip
. : var="*.zip" # var , "zip" echo "$var" # *.zip echo $var # , .zip
echo
. , , -n
, echo
, , . — printf
: printf "%s\n" "$foo"
$
, . Perl.=
, . . foo = bar
, . — foo
— . — .foo= bar # !
foo =bar # !
$foo = bar; # !
foo=bar # .
foo="bar" # .
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 '
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-, . , . .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
. }
;
.pushd
, popd
dirs
. , , cd
pwd
, . : find ... -type d -print0 | while IFS= read -r -d '' subdir; do here=$PWD cd "$subdir" && whatever && ... cd "$here" done
find ... -type d -print0 | while IFS= read -r -d '' subdir; do (cd "$subdir" || exit; whatever; ...) done
cd
. , , cd
. , ... && ...
, . ( ).==
POSIX- [
. =
[[
. [ bar = "$foo" ] && echo yes [[ bar == $foo ]] && echo yes
[ "$x" == y ]
, . — «» ( Bashism ). , [[
.;
&
. ;
. for i in {1..10}; do ./something & done : for i in {1..10}; do ./something & done
&
(command terminator), ;
. .;
, ;
.&&
||
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
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
true && { echo true; false; } || { echo false; true; }
bash: !": event not found
$ 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!'
set +H echo "Hello World!"
histchars=
set +H
set +o histexpand
~/.bashrc
, . , , . exmark='!' echo "Hello, world$exmark"
!
. , echo "Hello World!"
, : echo "Hello, World!(and the rest of the Universe)" echo "foo!'bar'"
$*
, $@
. , . : for arg in "$@" # : for arg
for arg
for arg in "$@"
. "$@"
— , ( ). 99% . # 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'
function
()
. Bash ( ) . (, zsh 4.x , , ). function foo
, : foo() { ... }
"~/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"
local
. . , ( $?
) , . . : local varname varname=$(command) rc=$?
=
.export
local
. ( Bash) export foo=~/bar
, ( dash) — . foo=~/bar; export foo # ! export foo="$HOME/bar" # !
$foo
. — $
. : foo="hello"; sed "s/$foo/good bye/"
[AZ]
[az]
. , , . , . , 3 .tr
. , '[' '[', - AZ az, ']' ']'. , . # , 26 LC_COLLATE=C tr AZ az # , . tr '[:upper:]' '[:lower:]'
$ ps ax | grep gedit 10530 ? S 6:23 gedit 32118 pts/0 R+ 0:00 grep gedit
ps ax | grep -v grep | grep gedit # ,
ps ax | grep '[g]edit' # , shell GLOB
[g]edit
, grep
gedit
. $ ps -C gedit PID TTY TIME CMD 10530 ? 00:06:23 gedit
pgrep
? $ pgrep gedit 10530
awk
cut
: $ ps -C gedit | awk '{print $1}' | tail -n1
ps
: $ ps -C gedit -opid= 10530
pgrep
, , pidof
( GNU/Linux): $ pidof gedit 10530
pkill
. , , , pgrep/pkill ssh
sshd, . $ 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
$foo
, \
%
. : printf %s "$foo" printf '%s\n' "$foo"
$n
, , . , runtime. : for ((i=1; i<=n; i++)); do ... done
for
, , (pre-expands), .=
[[, Bash , . bar
*, true. , : if [[ $foo = "$bar" ]]
=~
, , . . 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.
[
, . $foo
0 , 42 , , 1, . [ -n "$foo" ] [ -z "$foo" ] [ -n "$( - "$file")" ] # [[ , : [[ -n $foo ]] [[ -z $foo ]]
test –e
1, symlink. ( ), : # bash/ksh/zsh [[ -e "$broken_symlink" || -L "$broken_symlink" ]] # POSIX sh+test [ -e "$broken_symlink" ] || [ -L "$broken_symlink" ]
ed file <<<"g/d\{1,3\}/s//e/g"
word=abcde expr "$word" : ".\(.*\)" bcde
word=match expr "$word" : ".\(.*\)"
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
. , , . , , , .`...`
, $(...)
, $(<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
pipefail
PIPESTATUS
, ksh93 — pipefail
, mksh pipefail
, — PIPESTATUS
. , ksh93, read
NUL-../
.*.*
, ./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
somecmd >>logfile 2>&1
$?
, . , ( ), , : 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
$ 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]}))
$ echo 'a[$(echo injection >&2)]' | bash -c 'read num; echo $((num+1))' injection 1
$ IFS=, read -ra fields <<< "a,b," $ declare -p fields declare -a fields='([0]="a" [1]="b")'
$ IFS=, read -ra fields <<< "a,b,c" $ declare -p fields declare -a fields='([0]="a" [1]="b" [2]="c")'
$ input="a,b," $ IFS=, read -ra fields <<< "$input," $ declare -p fields declare -a fields='([0]="a" [1]="b" [2]="")'
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)
cd
stdout /home/user/some/dir
, , CDPATH, some command
../
. unset CDPATH
, , , CDPATH.Source: https://habr.com/ru/post/311762/
All Articles