diff --git a/src/spec2nexus/plugins/spec_common.py b/src/spec2nexus/plugins/spec_common.py
index c538de2..4dde007 100644
--- a/src/spec2nexus/plugins/spec_common.py
+++ b/src/spec2nexus/plugins/spec_common.py
@@ -656,7 +656,12 @@ class SPEC_PositionerNames(ControlLineHandler):
         key = text.split()[0]
         if key == "#O0":
             sdf_object.O = []       # TODO: What if motor names are different?
-        sdf_object.O.append(split_column_labels(strip_first_word(text)))
+        content = strip_first_word(text).strip()
+        if content == '':
+            content = []
+        else:
+            content = split_column_labels(content)
+        sdf_object.O.append(content)
 
 
 @six.add_metaclass(AutoRegister)
@@ -683,7 +688,12 @@ class SPEC_PositionerMnemonics(ControlLineHandler):
     def process(self, text, header, *args, **kws):
         if not hasattr(header, 'o'):
             header.o = []
-        header.o.append( strip_first_word(text).split() )
+        content = strip_first_word(text).strip()
+        if content == '':
+            content = []
+        else:
+            content = content.split()
+        header.o.append(content)
         header.addPostProcessor('positioner cross-referencing', self.postprocess)
     
     def postprocess(self, header, *args, **kws):
@@ -740,7 +750,12 @@ class SPEC_Positioners(ControlLineHandler):
     def process(self, text, scan, *args, **kws):
         if isinstance(scan, SpecDataFileHeader):
             scan = scan.getLatestScan()
-        scan.P.append( strip_first_word(text) )
+        content = strip_first_word(text)
+        if content == '':
+            content = []
+        else:
+            content = content.split()
+        scan.P.append(content)
         scan.addPostProcessor('motor_positions', self.postprocess)
     
     def postprocess(self, scan, *args, **kws):
@@ -754,7 +769,7 @@ class SPEC_Positioners(ControlLineHandler):
             if row >= len(scan.header.O):
                 scan.add_interpreter_comment('#P%d found without #O%d' % (row, row))
                 continue
-            for col, val in enumerate(values.split()):
+            for col, val in enumerate(values):
                 if col >= len(scan.header.O[row]):
                     scan.add_interpreter_comment(
                         'extra value in #P%d position %d, no matching label in #O%d' % (row, col+1, row)

empty #O or #P list should not return empty string
When the `#On` or `#Pn` control lines are parsed, if there are no positioners specified (see https://github.com/BCDA-APS/apstools/issues/222 and https://github.com/aps-8id-trr/ipython-8idiuser/issues/13), the result should be an empty list (`[]`).

Presently, `#O0 ` results in `[['']]` and `#P0 ` results in `[]`.
diff --git a/tests/data/issue196_data.txt b/tests/data/issue196_data.txt
new file mode 100644
index 0000000..2a6d81b
--- /dev/null
+++ b/tests/data/issue196_data.txt
@@ -0,0 +1,64 @@
+#F /tmp/tmp165tf5wf/20190814-234607.dat
+#E 1565844367
+#D Wed Aug 14 23:46:07 2019
+#C BlueSky  user = mintadmin  host = mint-vm
+#O0 
+#o0 
+
+#S 108  tune_mr()
+#D Thu May 02 17:45:33 2019
+#C Thu May 02 17:45:33 2019.  plan_type = generator
+#C Thu May 02 17:45:33 2019.  uid = 2ffe4d87-9f0c-464a-9d14-213ec71afaf7
+#MD uid = 2ffe4d87-9f0c-464a-9d14-213ec71afaf7
+#MD APSTOOLS_VERSION = 1.1.0
+#MD BLUESKY_VERSION = 1.5.2
+#MD EPICS_CA_MAX_ARRAY_BYTES = 1280000
+#MD EPICS_HOST_ARCH = linux-x86_64
+#MD OPHYD_VERSION = 1.3.3
+#MD beamline_id = APS USAXS 9-ID-C
+#MD datetime = 2019-05-02 17:45:33.904824
+#MD detectors = ['I0_USAXS']
+#MD login_id = usaxs@usaxscontrol.xray.aps.anl.gov
+#MD motors = ['m_stage_r']
+#MD pid = 29888
+#MD proposal_id = testing Bluesky installation
+#MD purpose = tuner
+#MD tune_md = {'width': -0.004, 'initial_position': 8.824977, 'time_iso8601': '2019-05-02 17:45:33.923544'}
+#MD tune_parameters = {'num': 31, 'width': -0.004, 'initial_position': 8.824977, 'peak_choice': 'com', 'x_axis': 'm_stage_r', 'y_axis': 'I0_USAXS'}
+#P0 
+#N 9
+#L m_stage_r  Epoch_float  Epoch  scaler0_time  scaler0_display_rate  m_stage_r_user_setpoint  m_stage_r_soft_limit_lo  m_stage_r_soft_limit_hi  I0_USAXS
+8.826977 1.2477173805236816 1 0.1 5.0 8.826977000000001 -7.856115 37.143885 127.0
+8.826844 1.6576123237609863 2 0.1 5.0 8.826843666666667 -7.856115 37.143885 126.0
+8.826711 2.0573489665985107 2 0.1 5.0 8.826710333333335 -7.856115 37.143885 127.0
+8.826577 2.473998785018921 2 0.1 5.0 8.826577 -7.856115 37.143885 126.0
+8.826443999999999 2.8916513919830322 3 0.1 5.0 8.826443666666668 -7.856115 37.143885 127.0
+8.826311 3.3084800243377686 3 0.1 5.0 8.826310333333334 -7.856115 37.143885 127.0
+8.826177 3.7085142135620117 4 0.1 5.0 8.826177000000001 -7.856115 37.143885 127.0
+8.826044 4.125398635864258 4 0.1 5.0 8.826043666666667 -7.856115 37.143885 127.0
+8.825911 4.541856050491333 5 0.1 5.0 8.825910333333335 -7.856115 37.143885 127.0
+8.825776999999999 4.960720539093018 5 0.1 5.0 8.825777 -7.856115 37.143885 127.0
+8.825644 5.3609373569488525 5 0.1 5.0 8.825643666666668 -7.856115 37.143885 127.0
+8.825510999999999 5.7742180824279785 6 0.1 5.0 8.825510333333334 -7.856115 37.143885 125.0
+8.825377 6.225730657577515 6 0.1 5.0 8.825377000000001 -7.856115 37.143885 127.0
+8.825244 6.642361402511597 7 0.1 5.0 8.825243666666667 -7.856115 37.143885 127.0
+8.825111 7.057381868362427 7 0.1 5.0 8.825110333333335 -7.856115 37.143885 127.0
+8.824977 7.45771598815918 7 0.1 5.0 8.824977 -7.856115 37.143885 126.0
+8.824843999999999 7.875597953796387 8 0.1 5.0 8.824843666666666 -7.856115 37.143885 127.0
+8.824711 8.304352283477783 8 0.1 5.0 8.824710333333334 -7.856115 37.143885 127.0
+8.824577 8.72563123703003 9 0.1 5.0 8.824577 -7.856115 37.143885 127.0
+8.824444 9.141692638397217 9 0.1 5.0 8.824443666666667 -7.856115 37.143885 127.0
+8.824311 9.572316646575928 10 0.1 5.0 8.824310333333333 -7.856115 37.143885 125.0
+8.824176999999999 9.975574493408203 10 0.1 5.0 8.824177 -7.856115 37.143885 128.0
+8.824044 10.392260074615479 10 0.1 5.0 8.824043666666666 -7.856115 37.143885 128.0
+8.823910999999999 10.808902025222778 11 0.1 5.0 8.823910333333334 -7.856115 37.143885 127.0
+8.823777 11.225586652755737 11 0.1 5.0 8.823777 -7.856115 37.143885 127.0
+8.823644 11.642327070236206 12 0.1 5.0 8.823643666666667 -7.856115 37.143885 127.0
+8.823511 12.057403802871704 12 0.1 5.0 8.823510333333333 -7.856115 37.143885 127.0
+8.823377 12.474164247512817 12 0.1 5.0 8.823377 -7.856115 37.143885 126.0
+8.823243999999999 12.891609191894531 13 0.1 5.0 8.823243666666666 -7.856115 37.143885 127.0
+8.823111 13.30853796005249 13 0.1 5.0 8.823110333333334 -7.856115 37.143885 127.0
+8.822977 13.725494146347046 14 0.1 5.0 8.822977 -7.856115 37.143885 127.0
+#C Thu May 02 17:45:48 2019.  num_events_baseline = 2
+#C Thu May 02 17:45:48 2019.  num_events_primary = 31
+#C Thu May 02 17:45:48 2019.  exit_status = success
diff --git a/tests/data/issue196_data2.txt b/tests/data/issue196_data2.txt
new file mode 100644
index 0000000..9ac8d32
--- /dev/null
+++ b/tests/data/issue196_data2.txt
@@ -0,0 +1,64 @@
+#F /tmp/tmp165tf5wf/20190814-234607.dat
+#E 1565844367
+#D Wed Aug 14 23:46:07 2019
+#C BlueSky  user = mintadmin  host = mint-vm
+#O0 m_stage_r
+#o0 mr
+
+#S 108  tune_mr()
+#D Thu May 02 17:45:33 2019
+#C Thu May 02 17:45:33 2019.  plan_type = generator
+#C Thu May 02 17:45:33 2019.  uid = 2ffe4d87-9f0c-464a-9d14-213ec71afaf7
+#MD uid = 2ffe4d87-9f0c-464a-9d14-213ec71afaf7
+#MD APSTOOLS_VERSION = 1.1.0
+#MD BLUESKY_VERSION = 1.5.2
+#MD EPICS_CA_MAX_ARRAY_BYTES = 1280000
+#MD EPICS_HOST_ARCH = linux-x86_64
+#MD OPHYD_VERSION = 1.3.3
+#MD beamline_id = APS USAXS 9-ID-C
+#MD datetime = 2019-05-02 17:45:33.904824
+#MD detectors = ['I0_USAXS']
+#MD login_id = usaxs@usaxscontrol.xray.aps.anl.gov
+#MD motors = ['m_stage_r']
+#MD pid = 29888
+#MD proposal_id = testing Bluesky installation
+#MD purpose = tuner
+#MD tune_md = {'width': -0.004, 'initial_position': 8.824977, 'time_iso8601': '2019-05-02 17:45:33.923544'}
+#MD tune_parameters = {'num': 31, 'width': -0.004, 'initial_position': 8.824977, 'peak_choice': 'com', 'x_axis': 'm_stage_r', 'y_axis': 'I0_USAXS'}
+#P0 8.824977
+#N 9
+#L m_stage_r  Epoch_float  Epoch  scaler0_time  scaler0_display_rate  m_stage_r_user_setpoint  m_stage_r_soft_limit_lo  m_stage_r_soft_limit_hi  I0_USAXS
+8.826977 1.2477173805236816 1 0.1 5.0 8.826977000000001 -7.856115 37.143885 127.0
+8.826844 1.6576123237609863 2 0.1 5.0 8.826843666666667 -7.856115 37.143885 126.0
+8.826711 2.0573489665985107 2 0.1 5.0 8.826710333333335 -7.856115 37.143885 127.0
+8.826577 2.473998785018921 2 0.1 5.0 8.826577 -7.856115 37.143885 126.0
+8.826443999999999 2.8916513919830322 3 0.1 5.0 8.826443666666668 -7.856115 37.143885 127.0
+8.826311 3.3084800243377686 3 0.1 5.0 8.826310333333334 -7.856115 37.143885 127.0
+8.826177 3.7085142135620117 4 0.1 5.0 8.826177000000001 -7.856115 37.143885 127.0
+8.826044 4.125398635864258 4 0.1 5.0 8.826043666666667 -7.856115 37.143885 127.0
+8.825911 4.541856050491333 5 0.1 5.0 8.825910333333335 -7.856115 37.143885 127.0
+8.825776999999999 4.960720539093018 5 0.1 5.0 8.825777 -7.856115 37.143885 127.0
+8.825644 5.3609373569488525 5 0.1 5.0 8.825643666666668 -7.856115 37.143885 127.0
+8.825510999999999 5.7742180824279785 6 0.1 5.0 8.825510333333334 -7.856115 37.143885 125.0
+8.825377 6.225730657577515 6 0.1 5.0 8.825377000000001 -7.856115 37.143885 127.0
+8.825244 6.642361402511597 7 0.1 5.0 8.825243666666667 -7.856115 37.143885 127.0
+8.825111 7.057381868362427 7 0.1 5.0 8.825110333333335 -7.856115 37.143885 127.0
+8.824977 7.45771598815918 7 0.1 5.0 8.824977 -7.856115 37.143885 126.0
+8.824843999999999 7.875597953796387 8 0.1 5.0 8.824843666666666 -7.856115 37.143885 127.0
+8.824711 8.304352283477783 8 0.1 5.0 8.824710333333334 -7.856115 37.143885 127.0
+8.824577 8.72563123703003 9 0.1 5.0 8.824577 -7.856115 37.143885 127.0
+8.824444 9.141692638397217 9 0.1 5.0 8.824443666666667 -7.856115 37.143885 127.0
+8.824311 9.572316646575928 10 0.1 5.0 8.824310333333333 -7.856115 37.143885 125.0
+8.824176999999999 9.975574493408203 10 0.1 5.0 8.824177 -7.856115 37.143885 128.0
+8.824044 10.392260074615479 10 0.1 5.0 8.824043666666666 -7.856115 37.143885 128.0
+8.823910999999999 10.808902025222778 11 0.1 5.0 8.823910333333334 -7.856115 37.143885 127.0
+8.823777 11.225586652755737 11 0.1 5.0 8.823777 -7.856115 37.143885 127.0
+8.823644 11.642327070236206 12 0.1 5.0 8.823643666666667 -7.856115 37.143885 127.0
+8.823511 12.057403802871704 12 0.1 5.0 8.823510333333333 -7.856115 37.143885 127.0
+8.823377 12.474164247512817 12 0.1 5.0 8.823377 -7.856115 37.143885 126.0
+8.823243999999999 12.891609191894531 13 0.1 5.0 8.823243666666666 -7.856115 37.143885 127.0
+8.823111 13.30853796005249 13 0.1 5.0 8.823110333333334 -7.856115 37.143885 127.0
+8.822977 13.725494146347046 14 0.1 5.0 8.822977 -7.856115 37.143885 127.0
+#C Thu May 02 17:45:48 2019.  num_events_baseline = 2
+#C Thu May 02 17:45:48 2019.  num_events_primary = 31
+#C Thu May 02 17:45:48 2019.  exit_status = success
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index e699fa6..f0cf294 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -189,6 +189,50 @@ class TestSpecificPlugins(unittest.TestCase):
                 nxentry["instrument/monochromator/wavelength"],
                 nxentry["sample/beam/incident_wavelength"],
                 )
+    
+    def test_empty_positioner(self):
+        "issue #196"
+        fname = os.path.join(_test_path, "tests", 'data', 'issue196_data.txt')
+        scan_number = 108
+        sdf = spec.SpecDataFile(fname)
+        scan = sdf.getScan(scan_number)
+
+        self.assertGreater(scan.header.raw.find("\n#O0 \n"), 0)
+        self.assertGreater(scan.header.raw.find("\n#o0 \n"), 0)
+        self.assertEqual(len(scan.header.O), 1)
+        self.assertEqual(len(scan.header.O[0]), 0)
+        self.assertEqual(len(scan.header.o), 1)
+        self.assertEqual(len(scan.header.o[0]), 0)
+        self.assertGreater(scan.raw.find("\n#P0 \n"), 0)
+        self.assertEqual(len(scan.P), 1)
+        self.assertEqual(len(scan.P[0]), 0)
+        self.assertEqual(len(scan.positioner), 0)
+    
+    def test_nonempty_positioner(self):
+        "issue #196"
+        fname = os.path.join(_test_path, "tests", 'data', 'issue196_data2.txt')
+        scan_number = 108
+        sdf = spec.SpecDataFile(fname)
+        scan = sdf.getScan(scan_number)
+
+        self.assertEqual(scan.header.raw.find("\n#O0 \n"), -1)
+        self.assertEqual(scan.header.raw.find("\n#o0 \n"), -1)
+        self.assertGreater(scan.header.raw.find("\n#O0 m_stage_r\n"), 0)
+        self.assertGreater(scan.header.raw.find("\n#o0 mr\n"), 0)
+        self.assertEqual(len(scan.header.O), 1)
+        self.assertEqual(len(scan.header.O[0]), 1)
+        self.assertEqual(scan.header.O[0][0], "m_stage_r")
+        self.assertEqual(len(scan.header.o), 1)
+        self.assertEqual(len(scan.header.o[0]), 1)
+        self.assertEqual(scan.header.o[0][0], "mr")
+        self.assertEqual(scan.raw.find("\n#P0 \n"), -1)
+        self.assertGreater(scan.raw.find("\n#P0 8.824977\n"), 0)
+        self.assertEqual(len(scan.P), 1)
+        self.assertEqual(len(scan.P[0]), 1)
+        self.assertEqual(scan.P[0][0], "8.824977")
+        self.assertEqual(len(scan.positioner), 1)
+        self.assertTrue("m_stage_r" in scan.positioner)
+        self.assertEqual(scan.positioner["m_stage_r"], float("8.824977"))
 
 
 def suite(*args, **kw):

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
coverage==7.2.7
cycler==0.11.0
docopt==0.6.2
exceptiongroup==1.2.2
execnet==2.0.2
fonttools==4.38.0
h5py==3.8.0
importlib-metadata==6.7.0
iniconfig==2.0.0
kiwisolver==1.4.5
lxml==5.3.1
matplotlib==3.5.3
numpy==1.21.6
packaging==24.0
Pillow==9.5.0
pluggy==1.2.0
pyparsing==3.1.4
pytest==7.4.4
pytest-asyncio==0.21.2
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-xdist==3.5.0
python-dateutil==2.9.0.post0
six==1.17.0
-e git+https://github.com/prjemian/spec2nexus.git@d9e160c501f367be8e9584ba9f8bf55bbc47b41e#egg=spec2nexus
tomli==2.0.1
typing_extensions==4.7.1
zipp==3.15.0

name: spec2nexus
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.2.7
      - cycler==0.11.0
      - docopt==0.6.2
      - exceptiongroup==1.2.2
      - execnet==2.0.2
      - fonttools==4.38.0
      - h5py==3.8.0
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - kiwisolver==1.4.5
      - lxml==5.3.1
      - matplotlib==3.5.3
      - numpy==1.21.6
      - packaging==24.0
      - pillow==9.5.0
      - pluggy==1.2.0
      - pyparsing==3.1.4
      - pytest==7.4.4
      - pytest-asyncio==0.21.2
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - pytest-xdist==3.5.0
      - python-dateutil==2.9.0.post0
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/spec2nexus

tests/test_plugin.py::TestSpecificPlugins::test_empty_positioner tests/test_plugin.py::TestSpecificPlugins::test_nonempty_positioner
tests/test_plugin.py::TestPlugin::test_handler_keys
tests/test_plugin.py::TestPlugin::test_sample_control_line_keys tests/test_plugin.py::TestCustomPlugin::test_custom_plugin tests/test_plugin.py::TestSpecificPlugins::test_geometry_plugin
src/spec2nexus/plugins/spec_common.py
src/spec2nexus/plugins/spec_common.py