Recursively count lines of code, excluding empty lines and comments
PowerShell, 47 46 bytes



ls *.sh -r|gc|%{$o+=!($_-match'^\s*(#|$)')};$o


Try it online! (will always return 0 since there aren't any files)

Try it online! (here's a link that populates a dummy file so you can see the process)

-1 byte thanks to Neil

ls is alias for Get-ChildItem, specifying *.sh with the -recurse parameter, then we get-content of files. For each of those lines |%{...}, we accumulate into our $output a one if the Boolean !(...) statement is truthy. Inside the statement is a simple regex -match against the whitespace-only/comment/blank lines. Finally we leave $o on the pipeline.

The implicit Write-Output that happens at program completion adds a trailing newline, but that shouldn't matter in this case because the variable $o itself doesn't have a trailing newline nor does the actual return variable. It's a quirk of the shell, not a quirk of the program. For example, saving this to a script and executing that script in a pipeline will not have a newline.