1

I need to apply a tag to all .md files in a directory (for use in Obsidian), including all .md files in subfolders. Building off the code in this post, I was able to get it to work for .md files in the same folder as the BAT file with this code:

for %%f in (*.md) do (        
      echo #unpolished >>%%f
)

If someone could modify it to apply to all subfolders, I would be grateful.

Though ideally, if it's not too difficult, could the code be adjusted to do these two things:

  1. Write to the beginning of the .md file.
  2. Instead of the #unpolished tag, could it add this code:
---
tags: unpolished
aliases:
cssclass:
---

The second part is not as significant if too difficult, and it's not timely if someone could add it later. I've tried various batch commands, VBA code, and Notebook++ but I have limited experience and have been unable to get it to work. Thanks for your help.

FiatMihi
  • 21
  • 5
  • This is not a code request service. Please open a Command Prompt window, type `for /?`, and press the `[ENTER]` key to quickly learn how including the subdirectories can be done. – Compo Apr 14 '23 at 15:45

3 Answers3

1

The general approach is to establish a filename-lister

for /f "delims=" %%b in ('dir /b /s /a-d filemask') do (
)

This executes the dir command, ignoring directorynames (/a-d) for files matching the filemask (*.md in your case), scanning subdirectories (/s) and listing in basic form (/b) that is, without headers, footers or details - names only. A list is built in memory of all of the files in the tree and then each filename found is assigned to %%b in turn.

The delims= turns off the default for /f processing into tokens (see for /? from the prompt for documentation - or hundreds of SO responses) so the entire filename is assigned to %%b

I prefer to avoid ADFNPSTXZ (in either case) as metavariables (loop-control variables) 
ADFNPSTXZ are also metavariable-modifiers which can lead to difficult-to-find bugs 
(See `for/f` from the prompt for documentation)

Since %%b contains the full absolute path to the file, it may contain spaces or other confounding characters, so best to "quote the filename" when used. which overcomes most problems.

Next step would be to create a temporary file containing your required prefix data as trying to insert at the start of a file is dicey.

echo --->tempfile.###
echo tags: unpolished>>tempfile.###
echo aliases:>>tempfile.###
echo cssclass:>>tempfile.###
echo --->>tempfile.###

would do that - note that there is only ONE > in the first of those lines. This starts a new file, so if there's an existing tempfile.###, it will be overwritten. The >> appends to the file.

BUT

That's highly inefficient as each echo statement opens, writes and closes the file.

(
 echo ---
 echo tags: unpolished
 echo aliases:
 echo cssclass:
 echo ---
)>tempfile.###

opens, writes and closes just once - and is easier to read and maintain as well. Note that the echoes are contained between parentheses (…).

Then append your selected file in %%b to the tempfile

type "%%b">>tempfile.###

so the tempfile then contains the preamble data and the original file,

and then replace the original using move tempfile original.

move tempfile.### "%%b"

Since this is a batch file, the /y switch to bypass confirmation of overwriting a file is not required, but there's no harm in including it.

move /y tempfile.### "%%b"

this will generate messages which can be suppressed by appending >nul and/or 2>nul to the move command line. >nul suppresses normal messages (1 file(s) moved) and 2>nul suppresses error messages.

And there you have it. Within the filename-lister, create the preamble in the tempfile, append the original to the tempfile and move the resultant tempfile over the original.

obligatory warning: Always verify against a test directory before applying to real data.

Useful extension:

If you run this process more than once, each file will acquire another copy of the preamble.

findstr /x /c:"tags: unpolished" "%%b" >nul
if errorlevel 1 (
 rem did not fine "tags: unpolished" as a line in the file, so insert it
 ...
) else (echo skipping "%%b" - already processed)

will attempt to find a line in the file %%b which exactly (/x) matches the constant string (/c:string) and set errorlevel to 0 if FOUND, hence don't process or non-0 if not found, hence process.

Magoo
  • 77,302
  • 8
  • 62
  • 84
1

Thanks all for your feedback. Redditor was able to provide a succinct code solution.

This code worked for me:

@echo off
setlocal enabledelayedexpansion
set "folder=your file path here"
for /r "%folder%" %%f in (*.md) do (
  (echo ---& echo tags: unpolished& echo aliases:& echo cssclass:& echo ---& echo. & type "%%f") > "%%~dpnf.tmp"
  move /y "%%~dpnf.tmp" "%%f" > nul
)

WARNING - for other users, create a backup of your directory before testing.

FiatMihi
  • 21
  • 5
-1

Here is an example of a perl script that will update all .md files in a given directory and its sub directories:

#! /usr/bin/env perl

use Object::Pad;
use feature qw(say);
use strict;
use warnings;

{
    die "Bad arguments. Please give start directory name" if @ARGV != 1;
    my $start_directory = shift @ARGV;
    my $self = Main->new(dir => $start_directory);
    $self->update_all_md_files_with_tag();

}

class Main;
field $_dir :param;
use feature qw(say);
use File::Find;

method update_all_md_files_with_tag() {
    find(sub {$self->wanted($_)}, $_dir);
    say "Done.";
}

method wanted($fn) {
    return if !(-f $fn);
    return if $fn !~ /\.md\z/;
    $self->add_tag($fn);
}

method read_file($fn) {
    open (my $fh, "<", $fn) or die "Could not open file '$fn': $!";
    my $str = do {local $/; <$fh>};
    close $fh;
    return $str;
}

method add_tag( $fn ) {
    my $content = $self->read_file($fn);
    $self->update_file_content_with_frontmatter(\$content);
    ## TODO: remember to take backup of all files before running this script!!
    $self->write_file( $fn, \$content );
    say ".. $fn";
}

method write_file( $fn, $content ) {
    open (my $fh, ">", $fn) or die "Could not open file '$fn': $!";
    print $fh $$content;
    close $fh;
}

method get_frontmatter_template {
    return <<"END";
---
tags: unpolished
aliases:
cssclass:
---
END
}

method update_file_content_with_frontmatter($content) {
    my $frontmatter = $self->get_frontmatter_template();
    $$content = $frontmatter . $$content;
}
Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174