151

Is there a way to call something like clang-format --style=Webkit for an entire cpp project folder, rather than running it separately for each file?

I am using clang-format.py and vim to do this, but I assume there is a way to apply this once.

Olivia Stork
  • 4,660
  • 5
  • 27
  • 40
user3639557
  • 4,791
  • 6
  • 30
  • 55

18 Answers18

154

Unfortunately, there is no way to apply clang-format recursively. *.cpp will only match files in the current directory, not subdirectories. Even **/* doesn't work.

Luckily, there is a solution: grab all the file names with the find command and pipe them in. For example, if you want to format all .h and .cpp files in the directory foo/bar/ recursively, you can do

find foo/bar/ -iname *.h -o -iname *.cpp | xargs clang-format -i

See here for additional discussion.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • 9
    Well, `**/*.cpp` seems to work in (reasonably modern) bash. You may need to `shopt -s globstar` before. – sbarzowski Mar 17 '16 at 10:23
  • 1
    If you are using CMake, [this post](http://www.labri.fr/perso/fleury/posts/programming/using-clang-tidy-and-clang-format.html) shows you how you can use CMake's [`GLOB_RECURSE`](https://cmake.org/cmake/help/latest/command/file.html) to find all `.cpp` files and pass them to `clang-format`. – phoenix Apr 30 '17 at 17:58
  • 6
    The combination of `find` and `xargs` should use `find ... -print0` and `xargs -0 ...` to ensure that all types of filenames are handled correctly. – Alexander Jan 26 '18 at 10:57
  • 15
    This command fails if any .cpp or .h files exist in pwd. `find foo/bar/ -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i` will fix the issue. – nyanpasu64 May 11 '18 at 06:43
  • @nyanpasu64 It will also fail in `zsh`. I actually don't understand why it doesn't fail in bash. :-) Those quotes should be really there so why does it even work without them in bash. – Anne van Rossum Aug 12 '22 at 13:24
  • I wrote a reusable POSIX sh script to identify C and/or C++ file paths as a workaround. I really wish clang-format would go ahead and implement built-in project recursion :/ https://github.com/mcandre/sail – mcandre Apr 12 '23 at 04:14
  • Here's archive to the post on using GLOB_RECURSE https://web.archive.org/web/20190727093603/http://www.labri.fr/perso/fleury/posts/programming/using-clang-tidy-and-clang-format.html – Aidan Aug 23 '23 at 16:21
80

What about:

clang-format -i -style=WebKit *.cpp *.h

in the project folder. The -i option makes it inplace (by default formatted output is written to stdout).

Tarick Welling
  • 3,119
  • 3
  • 19
  • 44
sbarzowski
  • 2,707
  • 1
  • 20
  • 25
  • 5
    or if you are using a config file and want to format multiple file types: `clang-format-3.6 -i -style=file *.cpp *.h *.hpp` – mBardos Aug 06 '15 at 12:46
  • 81
    Unfortunately, that won't recurse into subdirectories. – Antimony Mar 16 '16 at 20:59
  • 4
    Yes, *.cpp will be expanded by the shell. clang just needs a list of files. More advanced options (like recursive globbing) depend on the features of your shell. See http://unix.stackexchange.com/questions/49913/recursive-glob on how to use `**` construct. – sbarzowski Mar 17 '16 at 10:14
  • What about with `clang-tidy`? – Aaron Franke Jul 01 '20 at 04:09
  • @AaronFranke Using `*.cpp` to generate a list of files should work. The `clang-tidy` command allows passing multiple files. ` USAGE: clang-tidy [options] [... ] ` In practice that may not be the best way, though. Clang-tidy performs a much deeper analysis, so it requires the compiler flags for each file etc. When I used clang-tidy, I usually had a "compilation database" – a JSON file with the commands for each file and some script which went over it. It was a couple years back, maybe there is a better way now. – sbarzowski Jul 01 '20 at 18:12
  • What if my C++ files are in subfolders? – Aaron Franke Jul 02 '20 at 04:56
  • You can use `**`. See: unix.stackexchange.com/questions/49913/recursive-glob. – sbarzowski Jul 04 '20 at 13:05
  • does not work for me - only get `invalid argument` errors. Even with double `-` on `style` – vlad_tepesch Jul 15 '20 at 15:48
  • @vlad_tepesch What exactly doesn't work? What command are you running exactly and what is the full error message (you might want to use pastebin for that)? – sbarzowski Jul 16 '20 at 13:33
50

First create a .clang-format file if it doesn't exist:

clang-format -style=WebKit -dump-config > .clang-format

Choose whichever predefined style you like, or edit the resulting .clang-format file.

clang-format configurator is helpful.

Then run:

find . -regex '.*\.\(cpp\|hpp\|cc\|cxx\)' -exec clang-format -style=file -i {} \;

Other file extensions than cpp, hpp, cc and cxx can be used in the regular expression, just make sure to separate them with \|.

Alexander
  • 9,737
  • 4
  • 53
  • 59
  • For `-style=file` is there a way to specify a custom file path? I tried `-style=~/.clang-format` and it doesn't work. – Aaron Franke Jan 08 '20 at 21:02
  • Not that I know of, apart from changing the current directory. – Alexander Jan 08 '20 at 21:06
  • The ghetto solution I came up with is to make a function which first runs `cp ~/.clang-format .` then the find command in your answer. – Aaron Franke Jan 08 '20 at 21:09
  • Interesting but it doesn't work for me on macOS. But this version (with -E flag and without escaping special symbols does): `find -E . -regex '.*\.(cpp|hpp|cc|cxx)' -exec clang-format -style=file -i {} \;` – Anton Breusov Mar 01 '21 at 13:06
  • Thanks for the info, Anton Breusov. `-E` does not work here on Arch Linux, but `-regextype egrep` does. Does `-regextype egrep` work on macOS too? – Alexander Mar 03 '21 at 09:33
15

I recently found a bash-script which does exactly what you need:

https://github.com/eklitzke/clang-format-all

This is a bash script that will run clang-format -i on your code.

Features:

  • Finds the right path to clang-format on Ubuntu/Debian, which encode the LLVM version in the clang-format filename
  • Fixes files recursively
  • Detects the most common file extensions used by C/C++ projects

On Windows, I used it successfully in Git Bash and WSL.

Community
  • 1
  • 1
Julius Bullinger
  • 1,181
  • 11
  • 29
10

For the Windows users: If you have Powershell 3.0 support, you can do:

Get-ChildItem -Path . -Directory -Recurse |
    foreach {
        cd $_.FullName
        &clang-format -i -style=WebKit *.cpp
    }

Note1: Use pushd . and popd if you want to have the same current directory before and after the script

Note2: The script operates in the current working directory

Note3: This can probably be written in a single line if that was really important to you

Tim Meyer
  • 12,210
  • 8
  • 64
  • 97
9

When you use Windows (CMD) but don't want to use the PowerShell cannon to shoot this fly, try this:

for /r %t in (*.cpp *.h) do clang-format -i -style=WebKit "%t"

Don't forget to duplicate the two %s if in a cmd script.

Michel de Ruiter
  • 7,131
  • 5
  • 49
  • 74
5

The below script and process:

  1. works in Linux
  2. should work on MacOS
  3. works in Windows inside Git For Windows terminal with clang-format downloaded and installed.

Here's how I do it:

I create a run_clang_format.sh script and place it in the root of my project directory, then I run it from anywhere. Here's what it looks like:

run_clang_format.sh

#!/bin/bash

THIS_PATH="$(realpath "$0")"
THIS_DIR="$(dirname "$THIS_PATH")"

# Find all files in THIS_DIR which end in .ino, .cpp, etc., as specified
# in the regular expression just below
FILE_LIST="$(find "$THIS_DIR" | grep -E ".*(\.ino|\.cpp|\.c|\.h|\.hpp|\.hh)$")"

echo -e "Files found to format = \n\"\"\"\n$FILE_LIST\n\"\"\""

# Format each file.
# - NB: do NOT put quotes around `$FILE_LIST` below or else the `clang-format` command will 
#   mistakenly see the entire blob of newline-separated file names as a SINGLE file name instead 
#   of as a new-line separated list of *many* file names!
clang-format --verbose -i --style=file $FILE_LIST

Using --style=file means that I must also have a custom .clang-format clang-format specifier file at this same level, which I do.

Now, make your newly-created run_clang_format.sh file executable:

chmod +x run_clang_format.sh

...and run it:

./run_clang_format.sh

Here's a sample run and output for me:

~/GS/dev/eRCaGuy_PPM_Writer$ ./run_clang-format.sh 
Files found to format = 
"""
/home/gabriel/GS/dev/eRCaGuy_PPM_Writer/examples/PPM_Writer_demo/PPM_Writer_demo.ino
/home/gabriel/GS/dev/eRCaGuy_PPM_Writer/examples/PPM_Writer_demo2/PPM_Writer_demo2.ino
/home/gabriel/GS/dev/eRCaGuy_PPM_Writer/src/eRCaGuy_PPM_Writer.h
/home/gabriel/GS/dev/eRCaGuy_PPM_Writer/src/eRCaGuy_PPM_Writer.cpp
/home/gabriel/GS/dev/eRCaGuy_PPM_Writer/src/timers/eRCaGuy_TimerCounterTimers.h
"""
Formatting /home/gabriel/GS/dev/eRCaGuy_PPM_Writer/examples/PPM_Writer_demo/PPM_Writer_demo.ino
Formatting /home/gabriel/GS/dev/eRCaGuy_PPM_Writer/examples/PPM_Writer_demo2/PPM_Writer_demo2.ino
Formatting /home/gabriel/GS/dev/eRCaGuy_PPM_Writer/src/eRCaGuy_PPM_Writer.h
Formatting /home/gabriel/GS/dev/eRCaGuy_PPM_Writer/src/eRCaGuy_PPM_Writer.cpp
Formatting /home/gabriel/GS/dev/eRCaGuy_PPM_Writer/src/timers/eRCaGuy_TimerCounterTimers.h

You can find my run_clang_format.sh file in my eRCaGuy_PPM_Writer repository, and in my eRCaGuy_CodeFormatter repository too. My .clang-format file is there too.

References:

  1. My repository:
    1. Update: see here instead: https://github.com/ElectricRCAircraftGuy/eRCaGuy_CodeFormatter
    2. eRCaGuy_PPM_Writer repo
    3. run_clang_format.sh file
  2. My notes on how to use clang-format in my "git & Linux cmds, help, tips & tricks - Gabriel.txt" doc in my eRCaGuy_dotfiles repo (search the document for "clang-format").
  3. Official clang-format documentation, setup, instructions, etc! https://clang.llvm.org/docs/ClangFormat.html
  4. Download the clang-format auto-formatter/linter executable for Windows, or other installers/executables here: https://llvm.org/builds/
  5. Clang-Format Style Options: https://clang.llvm.org/docs/ClangFormatStyleOptions.html
  6. [my answer] How do I get the directory where a Bash script is located from within the script itself?

Related:

  1. [my answer] Indenting preprocessor directives with clang-format

See also:

  1. [my answer] https://stackoverflow.com/questions/67678531/fixing-a-simple-c-code-without-the-coments/67678570#67678570
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
2

Here is a solution that searches recursively and pipes all files to clang-format as a file list in one command. It also excludes the "build" directory (I use CMake), but you can just omit the "grep" step to remove that.

shopt -s globstar extglob failglob && ls **/*.@(h|hpp|hxx|c|cpp|cxx) | grep -v build | tr '\n' ' ' | xargs clang-format -i
Bim
  • 1,008
  • 1
  • 10
  • 29
2

In modern bash you can recursively crawl the file tree

for file_name in ./src/**/*.{cpp,h,hpp}; do
    if [ -f "$file_name" ]; then
        printf '%s\n' "$file_name"
        clang-format -i $file_name
    fi
done

Here the source is assumed to be located in ./src and the .clang-format contains the formatting information.

Mikhail
  • 7,749
  • 11
  • 62
  • 136
2

You can use this inside a Make file. It uses git ls-files --exclude-standard to get the list of the files, so that means untracked files are automatically skipped. It assumes that you have a .clang-tidy file at your project root.

format:
ifeq ($(OS), Windows_NT)
    pwsh -c '$$files=(git ls-files --exclude-standard); foreach ($$file in $$files) { if ((get-item $$file).Extension -in ".cpp", ".hpp", ".c", ".cc", ".cxx", ".hxx", ".ixx") { clang-format -i -style=file $$file } }'
else
    git ls-files --exclude-standard | grep -E '\.(cpp|hpp|c|cc|cxx|hxx|ixx)$$' | xargs clang-format -i -style=file
endif

Run with make format

Notice that I escaped $ using $$ for make.

If you use go-task instead of make, you will need this:

  format:
    - |
      {{if eq OS "windows"}} 
        powershell -c '$files=(git ls-files --exclude-standard); foreach ($file in $files) { if ((get-item $file).Extension -in ".cpp", ".hpp", ".c", ".cc", ".cxx", ".hxx", ".ixx") { clang-format -i -style=file $file } }' 
      {{else}} 
        git ls-files --exclude-standard | grep -E '\.(cpp|hpp|c|cc|cxx|hxx|ixx)$' | xargs clang-format -i -style=file 
      {{end}}

Run with task format

If you want to run the individual scripts, then use these

# powershell
$files=(git ls-files --exclude-standard); foreach ($file in $files) { if ((get-item $file).Extension -in ".cpp", ".hpp", ".c", ".cc", ".cxx", ".hxx", ".ixx") { clang-format -i -style=file $file } }
# bash
git ls-files --exclude-standard | grep -E '\.(cpp|hpp|c|cc|cxx|hxx|ixx)$' | xargs clang-format -i -style=file
Amin Ya
  • 1,515
  • 1
  • 19
  • 30
1

I'm using the following command to format all objective-C files under the current folder recursively:

$ find . -name "*.m" -o -name "*.h" | sed 's| |\\ |g' | xargs clang-format -i

I've defined the following alias in my .bash_profile to make things easier:

# Format objC files (*.h and *.m) under the current folder, recursively
alias clang-format-all="find . -name \"*.m\" -o -name \"*.h\" | sed 's| |\\ |g' | xargs clang-format -i"
marcelosalloum
  • 3,481
  • 4
  • 39
  • 63
1

As @sbarzowski touches on in a comment above, in bash you can enable globstar which causes ** to expand recursively.

If you just want it for this one command you can do something like the following to format all .h, .cc and .cpp files.

(shopt -s globstar; clang-format -i **/*.{h,cc,cpp})

Or you can add shopt -s globstar to your .bashrc and have ** goodness all the time in bash.

As a side note, you may want to use --dry-run with clang-format the first time to be sure it's what you want.

studgeek
  • 14,272
  • 6
  • 84
  • 96
0

I had similar issue with clang-format, we have a huge project with a lot of files to check and to reformat. Scripts were a ok solutions, but there was too slow.

So, I've wrote an application that can recursively going thru files in folder and executes clang-format on them in fast multithreaded manor. Application also supports ignore directories and files that you might not wanna touch by format (like thirdparty dirs)

You can checkout it from here: github.com/GloryOfNight/clang-format-all

I hope it would be also useful for other people.

ps: I know that app huge overkill, but its super fast at it job

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 07 '22 at 02:52
0

A bit <O/T>, but when I googled "how to feed a list of files into clang-format" this was the top hit. In my case, I don't want to recurse over an entire directory for a specific file type. Instead, I want to apply clang-format to all the files I edited before I push my feature/bugfix branch. The first step in our pipeline is clang-format, and it almost always fails, so I wanted to run this "manually" on my changes just to take care of that step instead of nearly always dealing with a quickly failing pipeline. You can get a list of all the files you changed with

git diff <commitOrTagToCompareTo> --name-only 

And borrowing from Antimony's answer, you can pipe that into xargs and finally clang-format:

git diff <commitOrTagToCompareTo> --name-only | xargs clang-format -i

Running git status will now show which files changed (git diff(tool) will show you the changes), and you can commit and push this up, hopefully moving on to more important parts of the pipeline.

yano
  • 4,827
  • 2
  • 23
  • 35
0

The first step is to find out header and source files, we use:

find . -path ./build -prune -o -iname "*.hpp" -o -iname "*.cpp" -o -iname "*.c" -o -iname "*.h"

The -o is for "or" and -iname is for ignoring case. And in your case specifically, you may add more extensions like -o -iname "*.cc". Here another trick is to escape ./build/ directory, -path ./build -prune suggests do not descend into the given directory "./build".
Type above command you will find it still prints out "./build", then we use sed command to replace "./build" with empty char, something like:

sed 's/.\/build//' <in stream>

At last, we call clang-format to do formatting:

clang-format -i <file>

Combine them, we have:

find . -path ./build -prune -o -iname "*.hpp" -o -iname "*.cpp" -o -iname "*.cc" -o -iname "*.cxx" -o -iname "*.c" -o -iname "*.h"|sed 's/.\/build//'|xargs clang-format -i
Hogan Chou
  • 11
  • 2
0

I had similar issue where I needed to check for formatting errors, but I wanted to do it with a single clang-format invocation both on linux and windows.

Here are my one-liners:

Bash:

find $PWD/src -type f \( -name "*.h" -o -name "*.cpp" \) -exec clang-format -style=file --dry-run --Werror {} +

Powershell:

clang-format -style=file --dry-run --Werror $(Get-ChildItem -Path $PWD/src -Recurse | Where Name -Match '\.(?:h|cpp)$' | Select-Object -ExpandProperty FullName)

FrogTheFrog
  • 1,548
  • 19
  • 32
0

This command will help you to find *.h & *.cpp from all the subfolders and also to update the files with our customize clang format file, which is in Foo/Boo

find ../src/ -iname *.h -o -iname *.cpp | xargs clang-format -style=file:"Foo/Boo/.clang-format" -i

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 16 '23 at 05:27
0

I checked these answers , and they all failed for me. (Some of them were not for Windows). So my solution in PowerShell that worked for me is:

Get-ChildItem -Recurse *.cpp |
     foreach {
         & 'C:\Program Files\LLVM\bin\clang-format.exe' -i -style=file --fallback-style=Google $_.FullName
     }

Get-ChildItem -Recurse *.h |
     foreach {
         & 'C:\Program Files\LLVM\bin\clang-format.exe' -i -style=file --fallback-style=Google $_.FullName
     }
H.M
  • 425
  • 2
  • 16