2017/11/27

コマンドライン文字列の解析処理(PythonもしくはGLibライブラリ)

コマンドライン形式の文字列を受け取って解析し、要素の集まりを得る処理を行いたい場面が存在するが、手動で行うのは面倒で、正確でない場合もありうる。

ここでは、Pythonの標準機能とGLibライブラリのそれぞれにおいてこの処理を行ってくれる関数についてを扱う。

元の記事は2010年1月に書かれたが、大幅に内容を書き直しており、GLibについての解説と例を追加している。

  1. Python
  2. GLib
    1. C言語での使用例
    2. Vala言語での使用例

Python

文字列オブジェクトのメンバ関数split()はスペースやクォートを含んだコマンドライン文字列を意図した通り(クォート内をひとまとめにしてその中のスペースでは区切らない)に区切ってくれない。

そこで、shlex.split()関数を用いて文字列を渡すことでスペースやクォートがうまく処理され、リスト型の戻り値として要素の集まりを得ることができる。以前試したときにある方にコメントで紹介していただいた方法。

[任意]ファイル名:parse-cmdline.py
#! /usr/bin/python

from __future__ import print_function

import shlex


str1 = 'aaa      bbb     ccc     "ddd     eee       fff"    ggg  "hhh  iii   "jjj'
str2 = 'aaa    bbb    ccc    "ddd   eee"fff    ggg  "h"hh   iii'
str3 = '    ls    -l  -a   "a b c.txt"'
str4 = '    ls    -l  -a   c"d e f.tx"t'

print ('"{0}" -> {1}'.format (str1, shlex.split (str1)))
print ('"{0}" -> {1}'.format (str2, shlex.split (str2)))
print ('"{0}" -> {1}'.format (str3, shlex.split (str3)))
print ('"{0}" -> {1}'.format (str4, shlex.split (str4)))
実行結果
"aaa      bbb     ccc     "ddd     eee       fff"    ggg  "hhh  iii   "jjj" -> ['aaa', 'bbb', 'ccc', 'ddd     eee       fff', 'ggg', 'hhh  iii   jjj']
"aaa    bbb    ccc    "ddd   eee"fff    ggg  "h"hh   iii" -> ['aaa', 'bbb', 'ccc', 'ddd   eeefff', 'ggg', 'hhh', 'iii']
"    ls    -l  -a   "a b c.txt"" -> ['ls', '-l', '-a', 'a b c.txt']
"    ls    -l  -a   c"d e f.tx"t" -> ['ls', '-l', '-a', 'cd e f.txt']

GLib

C言語やVala言語で使用可能なライブラリGLibのg_shell_parse_argv()(Vala言語ではGLib.Shell.parse_argv())も同様の処理を行う。

GLibはLGPLライセンスだが、MonoプロジェクトによるMITライセンスの実装eglibでもこの関数は利用できる。

C言語での使用例

文字列以外にgint型,gchar **型,GError *型の変数を用意してこの関数を呼び出し、引数の数と内容(構文解析エラーになった場合はその情報を含んだGError)を得る。

正常に処理が行われた場合、必要のなくなった内容部分(gchar **型)はg_strfreev()で解放する必要がある。

[任意]ファイル名:glib-parse-cmdline.c
/*
 * GLib (LGPL 2.1+):
 *   gcc -O2 -Wall -Wextra $(pkg-config --cflags glib-2.0) glib-parse-cmdline.c -o glib-parse-cmdline $(pkg-config --libs glib-2.0)
 *
 * eglib (MIT):
 *   gcc -O2 -Wall -Wextra -Ieglib -Ieglib/src glib-parse-cmdline.c eglib/src/.libs/libeglib.a -o glib-parse-cmdline-eglib
 */

#include <stdlib.h>
#include <glib.h>

gboolean
print_cmdline (const gchar *str)
{
  gint i;
  gint argc;
  gchar **argv;
  GError *error = NULL;

  g_shell_parse_argv (str, &argc, &argv, &error);

  if (error)
  {
    g_printerr ("g_shell_parse_argv () failed (%s): %s\n", str, error->message);
    g_error_free (error);
    return FALSE;
  }
  g_print ("\"%s\" -> ", str);
  for (i = 0; i < argc; i++)
  {
    g_print (i ? ", " : "[");
    g_print ("\"%s\"", argv[i]);
  }
  g_print ("]\n");
  g_strfreev (argv);

  return TRUE;
}

int
main ()
{
  const gchar *str1 = "aaa      bbb     ccc     \"ddd     eee       fff\"    ggg  \"hhh  iii   \"jjj";
  const gchar *str2 = "aaa    bbb    ccc    \"ddd   eee\"fff    ggg  \"h\"hh   iii";
  const gchar *str3 = "    ls    -l  -a   \"a b c.txt\"";
  const gchar *str4 = "    ls    -l  -a   c\"d e f.tx\"t";

  if (! print_cmdline (str1))
    return EXIT_FAILURE;
  if (! print_cmdline (str2))
    return EXIT_FAILURE;
  if (! print_cmdline (str3))
    return EXIT_FAILURE;
  if (! print_cmdline (str4))
    return EXIT_FAILURE;

  return EXIT_SUCCESS;
}
実行結果
"aaa      bbb     ccc     "ddd     eee       fff"    ggg  "hhh  iii   "jjj" -> ["aaa", "bbb", "ccc", "ddd     eee       fff", "ggg", "hhh  iii   jjj"]
"aaa    bbb    ccc    "ddd   eee"fff    ggg  "h"hh   iii" -> ["aaa", "bbb", "ccc", "ddd   eeefff", "ggg", "hhh", "iii"]
"    ls    -l  -a   "a b c.txt"" -> ["ls", "-l", "-a", "a b c.txt"]
"    ls    -l  -a   c"d e f.tx"t" -> ["ls", "-l", "-a", "cd e f.txt"]

Vala言語での使用例

GLib.Shell.parse_argv()は2番目の引数に文字列配列(未初期化でも可)をout指定で渡すことでこの配列に取得済みの内容が入る。これの解放処理は文字列配列の変数が寿命となったときに自動的に行われる。

[任意]ファイル名:parse-cmdline.vala
/*
 * valac --pkg posix parse-cmdline.vala -o parse-cmdline
 */

using Posix;

bool
print_cmdline (string str)
{
  try
  {
    string[] argv;
    GLib.Shell.parse_argv (str, out argv);
    print ("\"%s\" -> ", str);
    for (int i = 0; i < argv.length; i++)
    {
      print (i != 0 ? ", " : "[");
      print ("\"%s\"", argv[i]);
    }
    print ("]\n");
  }
  catch (GLib.ShellError e)
  {
    printerr ("GLib.Shell.parse_argv() failed (%s): %s\n", str, e.message);
    return false;
  }

  return true;
}

int
main (string[] args)
{
  const string str1 = "aaa      bbb     ccc     \"ddd     eee       fff\"    ggg  \"hhh  iii   \"jjj";
  const string str2 = "aaa    bbb    ccc    \"ddd   eee\"fff    ggg  \"h\"hh   iii";
  const string str3 = "    ls    -l  -a   \"a b c.txt\"";
  const string str4 = "    ls    -l  -a   c\"d e f.tx\"t";

  if (! print_cmdline (str1))
    return EXIT_FAILURE;
  if (! print_cmdline (str2))
    return EXIT_FAILURE;
  if (! print_cmdline (str3))
    return EXIT_FAILURE;
  if (! print_cmdline (str4))
    return EXIT_FAILURE;

  return EXIT_SUCCESS;
}

実行結果は上のC言語版と同じなので省略する。

使用したバージョン:
  • Python 2.7.14, 3.6.3
  • GLib 2.54.1
  • GCC 7.2.0
  • Vala 0.36.6