-
BASH > if, les conditions et les tests
IF …
renvoie 0 si l’expression est vraie, 1 si elle est fausse.
if test1 ; then ... elif test2 ; then ... else ... fi[ ou [[
[est la forme raccourci deTEST
[[est la forme raccourci deBASHet permet de faire des tests sur des RegEx :[[ $VAR =~ f{oo,ee,a}d ]]→food feed fad=~signale une RegExAvec [[, les variables n'ont pas besoin d'être quottées:
file="file name" [[ -f $file ]] && echo "$file is a file"
Les parentheses n'ont pas besoin d'être échappées:
[[ -f $file1 && ( -d $dir1 || -d $dir2) ]]
string comparisons using < or > respect the current locale when done in [[, but not in [ or test.
As a rule of thumb, [[ is used for strings and files. If you want to compare numbers, use an ArithmeticExpression, e.g.
i=0
while (( i < 10 )); do ...
Quand utiliser [[ et [
[ pour une portabilité avec POSIX ou BourneShell.
[[ pour BASH, Zsh or KornShell.
(( 1 + 1 == 2 ? 1 : $(echo "This doesn't do what you think..." >&2; echo 1) ))
[[ '1 + 1' -eq 2 && $(echo "...but this probably does what you expect." >&2) ]]
[ $((1 + 1)) -eq 2 -o $(echo 'No short-circuit' >&2) ]
[[ can detect the presence of expansions that don't result in a word yet still throw an error if none are specified. Ordinary commands can't.
( set -- $(echo 'Unquoted null expansions do not result in "null" parameters.' >&2); echo $# ) [[ -z $(:) ]] && echo '-z was supplied an arg and evaluated empty.' [ -z ] && echo '-z wasn't supplied an arg, and no errors are reported. There's no possible way Bash could enforce specifying an argument here.' [[ -z ]] # This will cause an error that ordinary commands can't detect.
For the very same reason, because ['s operators are just "arguments", unlike [[, you can specify operators as parameters to an ordinary test command. This might be seen as a limitation of [[, but the downsides outweigh the good almost always.
>(COMMAND)Process substitution<(COMMAND)Process substitution[ "$variable" ] || echo 'variable inon déclarée ou vide!' >&2
if [[ $file -nt ${file[1]} ]]; then printf 'file %s is newer than %s\n' "${file[@]}" fi
expression grouping (...) [[ $var = img* && ($var = *.png || $var = *.jpg) ]] && echo "$var starts with img and ends with .jpg or .png" Pattern matching [[ $name = a* ]] || echo "name does not start with an 'a': $name" RegularExpression matching =~ [[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th!" The -a and -o operators, and ( ... ) grouping, are deprecated. if [ "$a" = a ] && [ "$b" = b ]; then ... if [ "$a" = a ] || { [ "$b" = b ] && [ "$c" = c ];}; then ... Special primitives that [[ is defined to have, but [ may be lacking (depending on the implementation): file is newer/older than other file -nt / -ot [[ $file0 -nt $file1 ]] && echo "$file0 is newer than $file1" two files are the same -ef [[ $input -ef $output ]] && { echo "will not overwrite input file: $input"; exit 1; } negation ! [[ ! -u $file ]] && echo "$file is not a setuid file" But there are more subtle differences.
SI PAS :
-zétant l'opposé de-n, on peut écrire :if [ ! -n $VAR ]FORME CONDENSÉE
[ test ] && ...→ exécute...si test est VRAI[ test ] || ...→ exécute...si test est FAUX[ test ] && ... || ---→ exécute...si test est VRAI,---si test est FAUXET et OU
Les opérateurs && et || trouvent aussi leur utilité dans un contexte arithmétique:
$(( 1 && 2 ))→ 1$(( 3 && 0 ))→ 0$(( 4 || 0 ))→ 1$(( 0 || 0 ))→ 0ET
if [ $condition1 ] && [ $condition2 ] if [ $condition1 -a $condition2 ] if [[ $condition1 && $condition2 ]] if [[ $condition1 ]] && [[ $condition2 ]]
if [[ -n $1 ]] && [[ -r $1 ]] ; then echo "Le fichier existe et est lisible" fiif [ "$A" -eq 9 ] && [ "$B" -eq 4 ] ; then echo "$A est égal à 9 et $B est égal à 4" fia=toto ; b=gaga if [ "$a" = toto ] && [ "$b" = gaga ] ; then echo "$a est égal à toto et $b est égal à gaga" fi
OU
if [ $condition1 ] || [ $condition2 ] if [ $condition1 -o $condition2 ] if [[ $condition1 || $condition2 ]]
if [[ -z $1 ]] || [[ ! -r $1 ]] ; then echo "Soit $1 est vide, soit le fichier est illisible" exit 2 fi
PLUSIEURS CONDITIONS
if [ -f $VAR1 ] && [ -f $VAR2 ] && [ -f $VAR3 ] ; then ...
if [[ -f $VAR1 && -f $VAR2 && -f $VAR3 ]] ;
then ...if [ -f $VAR1 -a -f $VAR2 -a -f $VAR3 ];
then ...CASE
case $var in "1") // si $var vaut un ;; "2|3") // si $var vaut deux ou trois ;; *) // au sinon ;; esac
TESTER DES NOMBRES
[ A -eq B ]vrai si A et B sont égaux (equal)[ A -ne B ]vrai si A et B sont différents (not equal)[ A -lt B ]vrai si A est strictement inférieur à B (less than)[ A -le B ]vrai si A est inférieur ou égal à B (less or equal)[ A -gt B ]vrai si A est strictement supérieur à B (greater than)[ A -ge B ]vrai si A est supérieur ou égal à B (greater or equal)tester si la variable est de type numérique
VAR=$1 if [ "$(echo $VAR | grep "^[[:digit:]]*$")" ] ; then // VAR est numérique fi
[[ $VAR != +([0-9]) ]]
tester si la variable numérique est de type entier
if ((var)) 2>/dev/null ; then // VAR est un entier fi
TESTER DES CHAINES
NOTE : les chaines doivent être entourées par des guillemets.
if [ -n "$VAR" ] ; then→ $VAR pas videif [ -z "$VAR" ] ; then→ $VAR vide[ "$a" = "toto" ]→ si $a est égal à "toto"[[ "$a" == *"toto"* ]]→ si $a contient "toto"if grep -q "$substr" <<< "$fullstring"; then echo "Found"; fi
-q quiet mode
Les RegEx sont annoncées avec la tilde
~[[ $VAR =~ ^[0-9] ]] → Si $VAR commence par un chiffre
Casse insensible
[ "${VAR,,}" == "toto" ]→,,met $VAR en minuscule. Donc prendra en compte Toto, TOTO, tOtO, etc.VAR='Une chaine quelconque'; [[ ${VAR,,} == *"une"* ]] && echo "Trouvé" # * pour dire 0 ou plusieurs caractères
[ "chaine1" != "chaine2" ]: vrai si les deux chaînes sont différentes.TESTER DES FICHIERS / DOSSIERS
TESTER SI UN FICHIER EXISTE ET N'EST PAS VIDE :
[[ -s FICHIER ]] && echo "fichier existe et n'est pas vide" [[ -s FICHIER ]] || echo "fichier n'existe pas ou vide"
[ -e $F ]vrai si $F existe (fichier ou répertoire)[ -s $F ]vrai si $F existe et si sa taille est supérieure à zéro (non vide)[ -f $F ]vrai si $F existe et est un fichier[ -d $F ]vrai si $F existe et est un répertoire[ -r $F ]vrai si $F existe et est accessible en lecture[ -w $F ]vrai si $F existe et est accessible en écriture[ -x $F ]vrai si $F existe et est un fichier exécutable ou un répertoire accessible[ -d $F ]vrai si $F est un répertoire dans le répertoire courant[ -O $F ]vrai si $F appartient à l'utilisateur[ -G $F ]vrai si $F appartient au groupe de l'utilisateur[ -b $F ]vrai si $F est un périphérique de type bloc (disque, partition)[ -c $F ]vrai si $F est un périphérique de type caractère (terminal, modem, port parallèle)[ -L $F ]vrai si $F est un lien symbolique[ -p $F ]vrai si $F est un tube nommé[ A -nt B ]vrai si A et B existent et si A est plus récent que B[ A -ot B ]vrai si A et B existent et si A est plus ancien que B[ A -ef B ]vrai si A et B représentent un seul et même fichierextension de fichier
if [ ${FICH: -4} == ".txt" ]
ATTENTION, l'espace entre
FICH:et-4est obligatoire.ftester si ichiers .bak existent
FILES="$@" for f in $FILES ; do if [ -f ${f}.bak ] ; then //si un fichier .bak existe continue //on passe au fichier suivant fi cp $f $f.bak //exécuté seulement si .bak n'existe pas done
TESTER UNE LIGNE LUE OU UNE COMMANDE
Ligne lue
Si on lit un fichier ligne par ligne, et que l'on veut faire un grep dessus :
while read LIGNE ; do if echo $LIGNE | grep -q "foo" ; then echo "Trouvé" fi done < fichier
-qpour un mode silencieux (quiet)Commande
value=$( grep -ic "toto" /mon/fichier ) if [ $value -eq 1 ] ; then // trouvé fi
-ipour insensible à la casse-cpour compter les résultatstester un GREP
if grep -q "bla" fichier && ! grep -q "toto" fichier ; then #OK fi
-qmuet!pour inverserif grep -Fxq "$VAR" fichier ; then # OK fi
-F, --fixed-strings Interpret PATTERN as a list of fixed strings, separated by new-lines, any of which is to be matched.-x, --line-regexp Select only those matches that exactly match the whole line.-qmuetASTUCES
if [ "X$chaine1" = "X$chaine2" ]empêche un message d'erreur si une des variables devait être vide (les caractères "X" postfixés se neutralisent).Les combinaisons de tests
SI ! { test1 || test2 } ALORS commandes FIN : exécute les commandes si NI test1 NI test2 sont VRAIS
structure TANT QUE ... FAIRE
While va répéter la boucle tant que le test est vrai : while test; do action1 action2; done
Until va répéter la boucle jusqu'à ce que le test soit vrai : until test; do action1 action2; done
Pour incrémenter :
let $[ i+=1 ]---
Tests [ ]
Les tests sont réalisés avec la commande interne [ ].
Il doit toujours y avoir des espaces à l'intérieur de [ ] :[ $i -lt 10 ]
A l'aide des opérateurs && (ET) et || (OU) il est possible d'utiliser les tests pour exécuter des commandes selon des conditions :
[ $i -lt 10 ]&& echo $i
[ -f $fichier ] || echo $fichier n'est pas un fichier
Paramètres pour les tests [ ]
Chaînes :
s1 # Vrai si la chaîne n'est pas vide
s1 = s2 # Vrai si les chaînes sont identiques! condition # Retour l'opposé de la condition
Opérateurs dans les tests :
-a # AND ou ET
-o # OR ou OUCASE
case $variable in
1) echo "toto"
;;
2) echo "titi"
;;
*) echo "defaut"
esacFOR
for i in $* ; do
echo $i
done
for i in `ls` ; do
echo $i
doneAttention si vous avez des espaces dans des fichiers que vous souhaitez garder, il faut changer la variable IFS :
OLDIFS=${IFS} IFS=$(echo -en "\n\b") for i in $(grep chaine *) ; do vos_commandes $i done IFS=${OLDIFS} i=1 while [ $i -le 3 ] ; do echo $i i=`expr $i + 1` done In Bash, test and [ are builtins.
TESTER UN GREP
grep -q renvoie 0 si OK.
if grep -q PATTERN file.txt; then
echo found else echo not found fi
As a quick note, when you do something like if [ -z "$var" ]…, it turns out that [ is actually a command you're running, just like grep. On my system, it's /usr/bin/[. (Well, technically, your shell probably has it built-in, but that's an optimization. It behaves as if it were a command). It works the same way, [ returns a zero exit code for true, a non-zero exit code for false. (test is the same thing as [, except for the closing ])
The double bracket enables additional functionality.
For example, you can use && and || instead of -a and -o and there's a regular expression matching operator =~.echo f{oo,ee,a}d food feed fad for num in {000..2}; do echo "$num"; done 000 001 002 echo {00..8..2} 00 02 04 06 08 echo {D..T..4} D H L P T
Double parentheses are used for arithmetic operations:
((a++))
((meaning = 42))
for ((i=0; i<10; i++))
echo $((a + b + (14 * c)))and they enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability.
Single brackets are also used for array indices:
Curly brace are required for (most/all?) array references on the right hand side.
ephemient's comment reminded me that parentheses are also used for subshells. And that they are used to create arrays.
array=(1 2 3)
echo ${array[1]}
2Excellent! Since this tripped me up a little, I should point out that ${var/foo/bar} will only replace the first occurrence of 'foo' with 'bar' in the string represented by 'var'.
To replace all occurrences, just double the first forward slash. E.g.$var="foo_foo_baz"; echo ${var//foo/bar}; will print: bar_bar_baz. – JJC May 23 '12 at 14:34
4Also for completeness, I just came across this in an old script: $[expression] ; this is the old, deprecated arithmetic expression syntax for the newer, preferred syntax: $((expression)) – michael_n Oct 6 '12 at 7:53
1@DennisWilliamson Another use of curly braces in bash is creating sequences, as peripherally mentioned below (stackoverflow.com/a/8552128/2749397) As I would like to comment a bit this feature (as you didn't mention it ;-) I'm taking the liberty of using the most voted answer as a vehicle... Two examples of sequence literals: echo {01..12} -> 01 02 03 04 05 06 07 08 09 10 11 12 (note the initial zero); echo {C..Q} -> C D E F G H I J K L M N O P Q. Its main use is in loops, e.g., for cnt in {01..12} ; do ... ${cnt} ... ; done – gboffi Oct 24 '14 at 14:41
A single bracket ([) usually actually calls a program named [; man test or man [ for more info. Example:
$ VARIABLE=abcdef
$ if [ $VARIABLE == abcdef ] ; then echo yes ; else echo no ; fi
yesThe double bracket ([[) does the same thing (basically) as a single bracket, but is a bash builtin.
$ VARIABLE=abcdef
$ if [[ $VARIABLE == 123456 ]] ; then echo yes ; else echo no ; fi
noParentheses (()) are used to create a subshell. For example:
$ pwd
/home/user
$ (cd /tmp; pwd)
/tmp
$ pwd
/home/userAs you can see, the subshell allowed you to perform operations without affecting the environment of the current shell.
4a. Braces ({}) are used to unambiguously identify variables. Example:
$ VARIABLE=abcdef
$ echo Variable: $VARIABLE
Variable: abcdef
$ echo Variable: $VARIABLE123456
Variable:
$ echo Variable: ${VARIABLE}123456
Variable: abcdef1234564b. Braces are also used to execute a sequence of commands in the current shell context, e.g.
$ { date; top -b -n1 | head ; } >logfile
# 'date' and 'top' output are concatenated,
# could be useful sometimes to hunt for a top loader )$ { date; make 2>&1; date; } | tee logfile
# now we can calculate the duration of a build from the logfileThere is a subtle syntactic difference with ( ), though (see bash reference) ; essentially, a semicolon ; after the last command within braces is a must, and the braces {, } must be surrounded by spaces.
Well, [ is actually a builtin in Bash, but it is supposed to act like /bin/[ as opposed to the [[ builtin. [[ has different features, like more logical operations and different quoting roles. Additionally: single parentheses are also used for arrays, process substitution, and extended globs; double parentheses are used for arithmetic; curly braces {} are used for command grouping or multitudes of types of parameter expansion or brace expansion or sequence expansion. I'm sure I've missed some other uses too... – ephemient Feb 2 '10 at 22:46
Good catch. There are a million of 'em.
The double-equals in the expression if [ $VARIABLE == abcdef ] is a bashism that -- although it works -- should probably be avoided; either explicitly use bash (if [[ ...==...]]) or make it clear that you're using the more traditional conditional ( if [ "$VARIABLE" = "abcdef" ] ). Arguably, scripts should start out as simple and portable as possible, up until they really do need features specific to bash (for one reason or another). But in any case, the intent should be clear; "=" and "==" and "[[" and "[" do work differently and their usage should be consistent.
Curly Brackets
${variable} Parameter substitution
${!variable} Indirect variable reference
{ command1; command2; . . . commandN; } Block of code
{string1,string2,string3,...} Brace expansion
{a..z} Extended brace expansion
{} Text replacement, after find and xargsParentheses
( command1; command2 ) Command group executed within a subshell
Array=(element1 element2 element3) Array initialization
result=$(COMMAND) Command substitution, new style
>(COMMAND) Process substitution
<(COMMAND) Process substitutionDouble Parentheses
(( var = 78 )) Integer arithmetic
var=$(( 20 + 5 )) Integer arithmetic, with variable assignment
(( var++ )) C-style variable increment
(( var-- )) C-style variable decrement
(( var0 = var1<98?9:21 )) C-style ternary operationecho $SHELL /bin/bash ~:$ echo ${#SHELL} 9 ARRAY=(one two three) echo ${#ARRAY} 3 echo ${TEST:-test} test echo $TEST export TEST=a_string echo ${TEST:-test} a_string echo ${TEST2:-$TEST} a_string echo $TEST2 echo ${TEST2:=$TEST} a_string ~:$ echo $TEST2 a_string ~:$ export STRING="thisisaverylongname" ~:$ echo ${STRING:4} isaverylongname ~:$ echo ${STRING:6:5} avery ~:$ echo ${ARRAY[*]} one two one three one four ~:$ echo ${ARRAY[*]#one} two three four ~:$ echo ${ARRAY[*]#t} one wo one hree one four ~:$ echo ${ARRAY[*]#t*} one wo one hree one four ~:$ echo ${ARRAY[*]##t*} one one one four ~:$ echo $STRING thisisaverylongname ~:$ echo ${STRING%name} thisisaverylong ~:$ echo ${STRING/name/string} thisisaverylongstring
echo ${#ARRAY} displays three, because of the first element of the ARRAY contains three characters, not because it contains three elements! To print the number of elements use echo ${#ARRAY[@]}.
echo ${TEST:-test} I did not get it. Thanks
When should the new test command [[ be used, and when the old one [? If portability to the BourneShell is a concern, the old syntax should be used. If on the other hand the script requires BASH or KornShell, the new syntax is much more flexible.
shareimprove this answer
What is the difference between test, [ and [[ ?
[ ("test" command) and [[ ("new test" command) are used to evaluate expressions. [[ works only in Bash, Zsh and the Korn shell, and is more powerful; [ and test are available in POSIX shells. Here are some examples:
#POSIX
[ "$variable" ] || echo 'variable is unset or empty!' >&2
[ -f "$filename" ] || printf 'File does not exist or is not a regular file: %s\n' "$filename" >&2if [[ ! -e $file ]]; then
echo "File doesn't exist or is in an inaccessible directory or is a symlink to a file that doesn't exist: $file" >&2
fiif [[ $file -nt ${file[1]} ]]; then
printf 'file %s is newer than %s\n' "${file[@]}"
fiTo cut a long story short: test implements the old, portable syntax of the command. In almost all shells (the oldest Bourne shells are the exception), [ is a synonym for test (but requires a final argument of ]). Although all modern shells have built-in implementations of [, there usually still is an external executable of that name, e.g. /bin/[. POSIX defines a mandatory feature set for [, but almost every shell offers extensions to it. So, if you want portable code, you should be careful not to use any of those extensions.
[[ is a new improved version of it, and is a keyword, not a program. This makes it easier to use, as shown below. [[ is understood by KornShell, Zsh and BASH (e.g. 2.03), but not by other POSIX shell implementations (like posh, yash or dash) or the BourneShell .
Although [ and [[ have much in common, and share many expression operators like "-f", "-s", "-n", "-z", there are some notable differences. Here is a comparison list:
expression grouping
(...)
[[ $var = img* && ($var = *.png || $var = *.jpg) ]] && echo "$var starts with img and ends with .jpg or .png"Pattern matching
[[ $name = a* ]] || echo "name does not start with an 'a': $name"RegularExpression matching =~ [[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th!"
The -a and -o operators, and ( ... ) grouping, are deprecated.
if [ "$a" = a ] && [ "$b" = b ]; then ...
if [ "$a" = a ] || { [ "$b" = b ] && [ "$c" = c ];}; then ...Special primitives that [[ is defined to have, but [ may be lacking (depending on the implementation): file is newer/older than other file -nt / -ot [[ $file0 -nt $file1 ]] && echo "$file0 is newer than $file1"
two files are the same -ef [[ $input -ef $output ]] && { echo "will not overwrite input file: $input"; exit 1; }
negation
!
[[ ! -u $file ]] && echo "$file is not a setuid file"But there are more subtle differences.
Avec [[, les variables n'ont pa besoin d'être quottées:
file="file name"
[[ -f $file ]] && echo "$file is a file"
Les parentheses n'ont pas besoin d'être échappées:
[[ -f $file1 && ( -d $dir1 || -d $dir2) ]]string comparisons using < or > respect the current locale when done in [[, but not in [ or test.
As a rule of thumb, [[ is used for strings and files. If you want to compare numbers, use an ArithmeticExpression, e.g.
i=0
while (( i < 10 )); do ...When should the new test command [[ be used, and when the old one [? If portability to POSIX or the BourneShell is a concern, the old syntax should be used.
If on the other hand the script requires BASH, Zsh or KornShell, the new syntax is much more flexible.See the Tests and Conditionals chapter in the BashGuide.
Theory
The theory behind all of this is that [ is a simple command, whereas [[ is a compound command. [ receives its arguments as any other command would, but most compound commands introduce a special parsing context which is performed before any other processing. Typically this step looks for special reserved words or control operators specific to each compound command which split it into parts or affect control-flow. The Bash test expression's logical and/or operators can short-circuit because they are special in this way (as are e.g. ;;, elif, and else). Contrast with ArithmeticExpression, where all expansions are performed left-to-right in the usual way, with the resulting string being subject to interpretation as arithmetic.
The arithmetic compound command has no special operators. It has only one evaluation context - a single arithmetic expression. Arithmetic expressions have operators too, some of which affect control flow during the arithmetic evaluation step (which happens last).
(( 1 + 1 == 2 ? 1 : $(echo "This doesn't do what you think..." >&2; echo 1) ))
Test expressions on the other hand do have "operators" as part of their syntax, which lie on the other end of the spectrum (evaluated first).
[[ '1 + 1' -eq 2 && $(echo "...but this probably does what you expect." >&2) ]]
Old-style tests have no way of controlling evaluation because its arguments aren't special.
[ $((1 + 1)) -eq 2 -o $(echo 'No short-circuit' >&2) ]
Different error handling is made possible by searching for special compound command tokens before performing expansions. [[ can detect the presence of expansions that don't result in a word yet still throw an error if none are specified. Ordinary commands can't.
( set -- $(echo 'Unquoted null expansions do not result in "null" parameters.' >&2); echo $# ) [[ -z $(:) ]] && echo '-z was supplied an arg and evaluated empty.' [ -z ] && echo '-z wasn't supplied an arg, and no errors are reported. There's no possible way Bash could enforce specifying an argument here.' [[ -z ]] # This will cause an error that ordinary commands can't detect.
For the very same reason, because ['s operators are just "arguments", unlike [[, you can specify operators as parameters to an ordinary test command. This might be seen as a limitation of [[, but the downsides outweigh the good almost always.