32

In Linux shell, what does % do, as in:

for file in *.png.jpg; do
  mv "$file" "${file%.png.jpg}.jpg"
done
Nissim Nanach
  • 640
  • 6
  • 13
  • 4
    Every time I look this up, I usually find the right section of the bash man page by searching for `%%` or `##`, since that's memorable and rare enough to find the right section quickly. `man bash` `/##` – Peter Cordes Aug 31 '16 at 05:57
  • 3
    @PeterCordes I always rememer this by "It's 5%, so % is at the end and thus cuts from the end. And it's #5, so # is at the start and cuts from the start." But sometimes I even mix up this one… – glglgl Aug 31 '16 at 14:01
  • 2
    @glglgl: On a typical USA keyboard, `#` is immediately to the left of `$`, and `%` is immediately to the right. No mnemonic needed --- just look down. (That's probably at least part of why those symbols were chosen.) – Kevin J. Chase Aug 31 '16 at 19:17
  • @KevinJ.Chase Not everyone worldwide has a typical USA keyboard. But indeed, on a German keyboard, % is right of $ as well, that could be a start for memoizing. – glglgl Sep 01 '16 at 07:09

4 Answers4

32

When % is used in pattern ${variable%substring} it will return content of variable with the shortest occurance of substring deleted from back of variable.

This function supports wildcard patterns - that's why it accepts star (asterisk) as a substite for zero or more characters.

It should be mentioned that this is Bash specific - other linux shells don't neccessarily contain this function.

If you want to learn more about string manipulation in Bash, i highly suggest reading this page. Among other handy functions it - for example - explains what does %% do :)

Edit: I forgot to mention that when it's used in pattern $((variable%number)) or $((variable1%$variable2)) the % character will function as modulo operator. DavidPostill has more specific documentation links in his answer.

When % is used in different context, it should be recognized as regular character only.

Marek Rost
  • 2,056
  • 10
  • 16
  • 2
    The operator supports *wildcard patterns*, not regular expressions. – gardenhead Aug 31 '16 at 03:20
  • Like gardenhead mentioned - it supports glob patterns, not regular expressions. In regular expression a star means zero or more of the previous character. – slebetman Aug 31 '16 at 05:54
  • 5
    The guide you've linked to is very much *not* recommended [by most Unix & Linux stack exchange users](http://unix.stackexchange.com/q/305358/135943). I recommend the [Wooledge Bash Guide](http://mywiki.wooledge.org/BashGuide) instead. – Wildcard Aug 31 '16 at 07:13
  • 3
    The prefix and suffix removing operators are [standard](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02), so they should be usable in any `sh`-compatible shell, not only Bash. (though perhaps not in `csh` and the like). – ilkkachu Aug 31 '16 at 08:58
  • 1
    Another context in which `%` is used is in format specifiers for `printf`. – Dennis Williamson Aug 31 '16 at 21:36
9

Bash Reference Manual: Shell Parameter Expansion

${parameter%word}
${parameter%%word}

The word is expanded to produce a pattern just as in filename expansion. If the pattern matches a trailing portion of the expanded value of parameter, then the result of the expansion is the value of parameter with the shortest matching pattern (the ‘%’ case) or the longest matching pattern (the ‘%%’ case) deleted. If parameter is ‘@’ or ‘*’, the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with ‘@’ or ‘*’, the pattern removal operation is applied to each member of the array in turn, and the expansion is the resultant list.

Steven
  • 27,531
  • 11
  • 97
  • 118
7

By experimenting, I find that a match after % is discarded, when the string is enclosed in curly brackets (braces).

To illustrate:

touch abcd         # Create file abcd

for file in ab*; do
 echo $file        # echoes the filename
 echo $file%       # echoes the filename plus "%"
 echo ${file%}     # echoes the filename
 echo "${file%}"   # echoes the filename
 echo
 echo "${file%c*}" # Discard anything after % matching c*
 echo "${file%*}"  # * is not greedy
 echo ${file%c*}   # Without quotes works too
 echo "${file%c}"  # No match after %, no effect
 echo $file%c*     # Without {} fails
done

Here is the output:

abcd
abcd%
abcd
abcd

ab
abcd
ab
abcd
abcd%c*
Nissim Nanach
  • 640
  • 6
  • 13
7

In Linux shell (bash), what does % do?

for file in *.png.jpg; do
  mv "$file" "${file%.png.jpg}.jpg"
done

In this particular case, the % is pattern matching operator (note it can also be a modulo operator).


Pattern Matching Operator

${var%$Pattern}, ${var%%$Pattern}

${var%$Pattern} Remove from $var the shortest part of $Pattern that matches the back end of $var.

${var%%$Pattern} Remove from $var the longest part of $Pattern that matches the back end of $var.

Example: Pattern matching in parameter substitution

#!/bin/bash
# patt-matching.sh

# Pattern matching  using the # ## % %% parameter substitution operators.

var1=abcd12345abc6789
pattern1=a*c  # * (wild card) matches everything between a - c.

echo
echo "var1 = $var1"           # abcd12345abc6789
echo "var1 = ${var1}"         # abcd12345abc6789
                              # (alternate form)
echo "Number of characters in ${var1} = ${#var1}"
echo

echo "pattern1 = $pattern1"   # a*c  (everything between 'a' and 'c')
echo "--------------"
echo '${var1#$pattern1}  =' "${var1#$pattern1}"    #         d12345abc6789
# Shortest possible match, strips out first 3 characters  abcd12345abc6789
#                                     ^^^^^               |-|
echo '${var1##$pattern1} =' "${var1##$pattern1}"   #                  6789      
# Longest possible match, strips out first 12 characters  abcd12345abc6789
#                                    ^^^^^                |----------|

echo; echo; echo

pattern2=b*9            # everything between 'b' and '9'
echo "var1 = $var1"     # Still  abcd12345abc6789
echo
echo "pattern2 = $pattern2"
echo "--------------"
echo '${var1%pattern2}  =' "${var1%$pattern2}"     #     abcd12345a
# Shortest possible match, strips out last 6 characters  abcd12345abc6789
#                                     ^^^^                         |----|
echo '${var1%%pattern2} =' "${var1%%$pattern2}"    #     a
# Longest possible match, strips out last 12 characters  abcd12345abc6789
#                                    ^^^^                 |-------------|

# Remember, # and ## work from the left end (beginning) of string,
#           % and %% work from the right end.

echo

exit 0

Source Parameter Substitution


Modulo Operator

%

modulo, or mod (returns the remainder of an integer division operation)

bash$ expr 5 % 3
2

5/3 = 1, with remainder 2

Source Operators

DavidPostill
  • 153,128
  • 77
  • 353
  • 394
  • Maybe I got it: this is for things like `.*` and `%` defines it to be non-greedy whereas `%%` makes it greedy? So actually in the rename example it doesn't matter whether to use `%` or `%%` but if it were `mv "$file" "${file%.*png.jpg}.jpg"` (note the *) using %% would rename all files to just `.jpg`, right? – Thomas Weller Aug 30 '16 at 22:46
  • @ThomasWeller I think that's correct. But I'm no bash expert. – DavidPostill Aug 30 '16 at 22:47
  • Saying "Remove ... the shortest part of `$Pattern`" is wrong as the variable `$Pattern` is not defined in the example, only the text "Pattern" is being removed from `$var` when doing `${var%Pattern}` or `${var%%Pattern}`. Maybe this is just a typo, but it's yet another example of tldp.org being wrong. [BashGuide](http://mywiki.wooledge.org/BashGuide) or [Bash Hackers Wiki](http://wiki.bash-hackers.org/) are both much better references imho. – John B Sep 01 '16 at 00:04
  • @JohnB Thanks. Both links bookmarked. I've corrected the text in the answer. – DavidPostill Sep 01 '16 at 08:17