Since you mention familiarity with/access to Python, and I assume you want to process batches from
.sdf from a script rather than from a GUI (note, ChemAxon offers a dedicated Markush Editor in addition to a plugin for their sketcher, Marvin (a tutorial)). Thus, I suggest using RDKit. In case you have access to a Linux distribution of Debian (or a relative of Debian like Ubuntu), there is a repackaged version by DebiChem (tracker), too. Then you do not need to install a separate instance of Miniconda/Anaconda (some notes about this on Matter Modeling Stack Exchange).
The following works well within a Jupyter notebook by Linux Debian 12/bookworm (branch testing) offering Python (version 3.9.9) and RDKit (version 2021.09.2). The credit is due to Greg Landrum, who posted the bits and bolts required for this to work and only rearranged/paraphrased for this answer.
Ad rem. In the first cell, initiate the Jupyter notebook:
from rdkit import Chem
from rdkit.Chem.Draw import rdDepictor
from rdkit.Chem import Draw
from rdkit.Chem.Draw import IPythonConsole
from rdkit.Chem import rdMolEnumerator
.sdf to process, e.g.
pv2 = Chem.MolFromMolBlock('''
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 10 8 0 0 0
M V30 BEGIN ATOM
M V30 1 C -1.7083 2.415 0 0
M V30 2 C -3.042 1.645 0 0
M V30 3 C -3.042 0.105 0 0
M V30 4 N -1.7083 -0.665 0 0
M V30 5 C -0.3747 0.105 0 0
M V30 6 C -0.3747 1.645 0 0
M V30 7 * -3.042 0.875 0 0
M V30 8 F -5.0434 0.875 0 0
M V30 9 * -1.0415 2.03 0 0
M V30 10 Cl -1.0415 4.34 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 2 2 3
M V30 3 1 3 4
M V30 4 2 4 5
M V30 5 1 5 6
M V30 6 2 1 6
M V30 7 1 7 8 ENDPTS=(2 2 3) ATTACH=ANY
M V30 8 1 9 10 ENDPTS=(2 1 6) ATTACH=ANY
M V30 END BOND
M V30 END CTAB
where grey clouds mark where variation of the position of the substituents is anticipated. By subsequent
pv2_bundle = rdMolEnumerator.Enumerate(pv2)
you launch the variation of the positions, with the results displayed as
If you want, a subsequent list comprehension offers you to access the result e.g., in SMILES strings (export as .sdf (v3000) equally is possible, check RDKit's documentation and cookbook):
listing = 
listing = [rdkit.Chem.MolToSmiles(entry) for entry in pv2_bundle]
for element in listing:
with output of
Again, the above only is rearranging an example by Greg Landrum's blog post detailing out multiple additional equally interesting examples of application.
As for the generation of the
.sdf (v3000) with «Markush syntax» with a GUI, beside mentioned Marvin by ChemAxon, BIOVIA Draw by Dassault offers this functionality, too. The later bears some similarity to MDL's ISIS Draw the more senior readers of chemistry.se still might remember/use.