0

I have three markdown files on my machine:

$ ls | grep md
bar.md
baz.md
foo.md

Each file has the given content:

$ cat *md
i am bar
i am baz
i am foo

As an example, I'd like "loop over all the files matching the *.md pattern and print the pattern: file_name: file_contents".

I'm most comfortable working with ruby. But I'd like to practice my bash for-loops and also develop my awk skills.

Ruby is the easiest for me:

#!/usr/bin/env ruby
# iterate over all markdown files in current directory

Dir.glob('*.md').each do |some_file|
  puts "#{some_file}: #{File.read(some_file)}"
end

output:

$ ./iterate_over_files.rb
bar.md: i am bar
baz.md: i am baz
foo.md: i am foo

And from following the linked bash resource I created the following for-loop:

#!/usr/bin/env bash
# iterate over all markdown files in current directory

for some_file in *.md
do
  echo $some_file: `cat $some_file`
done

which gives the same output:

$ diff <(./iterate_over_files.sh) <(./iterate_over_files.rb) # no difference

I know from watching a presentation by Brian Kernaghan and from reviewing his slides that this is a great chance to use awk because it follows the pattern:

for each file
  for each input line 
    for each pattern
      if the pattern matches input line
        do the action
  • How do I leverage this pattern using awk and iterate over all the markdown files and print a formatted result?
  • Your code echo $some_file: `cat $some_file` would be better written echo "$some_file:" $(cat "$some_file"). The $(...) is a modern replacement for `` ... ``, and you should always double-quote variables (or interpolated variables) unless you know you have a specific exception. (Strangely, this is one of them, where the $(...) should probably not be included in the quoted string.) – roaima Mar 28 '17 at 23:25
2

According to the bash manual, you should use $(< FILE) rather than $(cat FILE). But there is no need to use command substitution at all:

for some_file in *.md
do
  echo -n "$some_file: "
  cat $some_file
done

Since you are already aware of process substitution, you could also do something like:

for some_file in *.md
do
  cat <(echo -n "$some_file: ") $some_file
done
3

How about this, processes each file and outputs the filename by using the FILENAME variable.

$ awk '{print FILENAME":",$0}' *.md
bar.md: i am bar
baz.md: i am baz
foo.md: i am foo
$

Or this one, little harder on the eyes, but just sets $0 to the filename followed by the file contents. Default action for awk is to print $0, so that's why it doesn't need a print.

$ awk '$0=FILENAME": "$0' *.md
bar.md: i am bar
baz.md: i am baz
foo.md: i am foo
$

Another approach. Instead of using FILENAME, uses the ARGV array of arguments.

$ awk '$0=ARGV[++z]": "$0' *.md
bar.md: i am bar
baz.md: i am baz
foo.md: i am foo
$
  • The first example makes sense: you're using the FILENAME builtin and printing the entire record with $0. But can you add an explanation for the second example? It seems like you're changing the value of $0 yet it's continuing to print two different results. – mbigras Mar 28 '17 at 21:32
  • 1
    Thank you for the edit. I'm still confused on the order. Based on your code I'd expect the first row to be bar.md: bar.md – mbigras Mar 28 '17 at 21:37
  • If the files have several lines in them, and you only want the first line prepended with the filename, you could use BEGINFILE like this: awk 'BEGINFILE { printf "%s: ", FILENAME } { print }' – Victor Jerlin Mar 28 '17 at 21:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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