26

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

for file in *.png.jpg; do
  mv "$file" "${file%.png.jpg}.jpg"
done
  • 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 5: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 1 '16 at 7:09
27

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.

  • 2
    The operator supports wildcard patterns, not regular expressions. – gardenhead Aug 31 '16 at 3: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 5:54
  • 5
    The guide you've linked to is very much not recommended by most Unix & Linux stack exchange users. I recommend the Wooledge Bash Guide instead. – Wildcard Aug 31 '16 at 7:13
  • 3
    The prefix and suffix removing operators are standard, 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 8: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.

  • 2
    N.B. some parameter expansions including % are posix features that many shells support. – kojiro Aug 30 '16 at 22:34
6

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*
6

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

  • 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 or Bash Hackers Wiki are both much better references imho. – John B Sep 1 '16 at 0:04
  • @JohnB Thanks. Both links bookmarked. I've corrected the text in the answer. – DavidPostill Sep 1 '16 at 8:17

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.