1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00

Improve String and StringList modules.

This commit is contained in:
David Steele 2017-12-22 19:30:15 -05:00
parent 5667932063
commit f89b027dec
10 changed files with 368 additions and 99 deletions

View File

@ -13,18 +13,20 @@ Contains information about the string
***********************************************************************************************************************************/
struct String
{
MemContext *memContext;
size_t size;
char *buffer;
};
/***********************************************************************************************************************************
Create a new string from another string
Create a new string from a zero-terminated string
***********************************************************************************************************************************/
String *
strNew(const char *string)
{
// Create object
String *this = memNew(sizeof(String));
this->memContext = memContextCurrent();
this->size = strlen(string);
// Allocate and assign string
@ -43,6 +45,7 @@ strNewFmt(const char *format, ...)
{
// Create object
String *this = memNew(sizeof(String));
this->memContext = memContextCurrent();
// Determine how long the allocated string needs to be
va_list argumentList;
@ -60,6 +63,28 @@ strNewFmt(const char *format, ...)
return this;
}
/***********************************************************************************************************************************
Create a new string from a string with a specific length
The string may or may not be zero-terminated but we'll use that nomeclature since we're not concerned about the end of the string.
***********************************************************************************************************************************/
String *
strNewSzN(const char *string, size_t size)
{
// Create object
String *this = memNew(sizeof(String));
this->memContext = memContextCurrent();
this->size = size;
// Allocate and assign string
this->buffer = memNewRaw(this->size + 1);
strncpy(this->buffer, string, this->size);
this->buffer[this->size] = 0;
// Return buffer
return this;
}
/***********************************************************************************************************************************
Append a string
***********************************************************************************************************************************/
@ -70,9 +95,13 @@ strCat(String *this, const char *cat)
int sizeGrow = strlen(cat);
// Allocate and append string
this->buffer = memGrowRaw(this->buffer, this->size + sizeGrow + 1);
strcpy(this->buffer + this->size, cat);
MEM_CONTEXT_BEGIN(this->memContext)
{
this->buffer = memGrowRaw(this->buffer, this->size + sizeGrow + 1);
}
MEM_CONTEXT_END();
strcpy(this->buffer + this->size, cat);
this->size += sizeGrow;
return this;
@ -91,7 +120,11 @@ strCatFmt(String *this, const char *format, ...)
va_end(argumentList);
// Allocate and append string
this->buffer = memGrowRaw(this->buffer, this->size + sizeGrow + 1);
MEM_CONTEXT_BEGIN(this->memContext)
{
this->buffer = memGrowRaw(this->buffer, this->size + sizeGrow + 1);
}
MEM_CONTEXT_END();
va_start(argumentList, format);
vsnprintf(this->buffer + this->size, sizeGrow + 1, format, argumentList);
@ -103,6 +136,34 @@ strCatFmt(String *this, const char *format, ...)
return this;
}
/***********************************************************************************************************************************
Duplicate a string from an existing string
***********************************************************************************************************************************/
String *
strDup(const String *this)
{
String *result = NULL;
if (this != NULL)
result = strNew(strPtr(this));
return result;
}
/***********************************************************************************************************************************
Are two strings equal?
***********************************************************************************************************************************/
bool
strEq(const String *this1, const String *this2)
{
bool result = false;
if (this1->size == this2->size)
result = strcmp(strPtr(this1), strPtr(this2)) == 0;
return result;
}
/***********************************************************************************************************************************
Return string ptr
***********************************************************************************************************************************/
@ -121,12 +182,64 @@ strSize(const String *this)
return this->size;
}
/***********************************************************************************************************************************
Return string size
***********************************************************************************************************************************/
String *
strTrim(String *this)
{
// Nothing to trim if size is zero
if (this->size > 0)
{
// Find the beginning of the string skipping all whitespace
char *begin = this->buffer;
while (*begin != 0 && (*begin == ' ' || *begin == '\t' || *begin == '\r' || *begin == '\n'))
begin++;
// Find the end of the string skipping all whitespace
char *end = this->buffer + (this->size - 1);
while (end > begin && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n'))
end--;
// Is there anything to trim?
size_t newSize = (size_t)(end - begin + 1);
if (begin != this->buffer || newSize < strSize(this))
{
// Calculate new size
this->size = newSize;
// Move the substr to the beginning of the buffer
memmove(this->buffer, begin, this->size);
this->buffer[this->size] = 0;
MEM_CONTEXT_BEGIN(this->memContext)
{
// Resize the buffer
this->buffer = memGrowRaw(this->buffer, this->size + 1);
}
MEM_CONTEXT_END();
}
}
return this;
}
/***********************************************************************************************************************************
Free the string
***********************************************************************************************************************************/
void
strFree(String *this)
{
memFree(this->buffer);
memFree(this);
if (this != NULL)
{
MEM_CONTEXT_BEGIN(this->memContext)
{
memFree(this->buffer);
memFree(this);
}
MEM_CONTEXT_END();
}
}

View File

@ -14,10 +14,16 @@ Functions
***********************************************************************************************************************************/
String *strNew(const char *string);
String *strNewFmt(const char *format, ...);
size_t strSize(const String *this);
String *strNewSzN(const char *string, size_t size);
String *strCat(String *this, const char *cat);
String *strCatFmt(String *this, const char *format, ...);
String *strDup(const String *this);
bool strEq(const String *this1, const String *this2);
const char *strPtr(const String *this);
size_t strSize(const String *this);
String *strTrim(String *this);
void strFree(String *this);
#endif

View File

@ -18,13 +18,74 @@ strLstNew()
return (StringList *)lstNew(sizeof(String *));
}
/***********************************************************************************************************************************
Split a string into a string list based on a delimiter
***********************************************************************************************************************************/
StringList *
strLstNewSplit(const String *string, const String *delimiter)
{
// Create the list
StringList *this = strLstNew();
// Base points to the beginning of the string that is being searched
const char *stringBase = strPtr(string);
// Match points to the next delimiter match that has been found
const char *stringMatch = NULL;
do
{
// Find a delimiter match
stringMatch = strstr(stringBase, strPtr(delimiter));
// If a match was found then add the string
if (stringMatch != NULL)
{
strLstAdd(this, strNewSzN(stringBase, stringMatch - stringBase));
stringBase = stringMatch + strSize(delimiter);
}
// Else make whatever is left the last string
else
strLstAdd(this, strNew(stringBase));
}
while(stringMatch != NULL);
return this;
}
/***********************************************************************************************************************************
Duplicate a string list
***********************************************************************************************************************************/
StringList *
strLstDup(const StringList *sourceList)
{
// Create the list
StringList *this = strLstNew();
// Copy variants
for (unsigned int listIdx = 0; listIdx < strLstSize(sourceList); listIdx++)
strLstAdd(this, strLstGet(sourceList, listIdx));
return this;
}
/***********************************************************************************************************************************
Wrapper for lstAdd()
***********************************************************************************************************************************/
StringList *
strLstAdd(StringList *this, String *string)
{
return (StringList *)lstAdd((List *)this, &string);
String *stringCopy = strDup(string);
return (StringList *)lstAdd((List *)this, &stringCopy);
}
/***********************************************************************************************************************************
Add a zero-terminated string to the list
***********************************************************************************************************************************/
StringList *
strLstAddZ(StringList *this, const char *string)
{
return strLstAdd(this, strNew(string));
}
/***********************************************************************************************************************************
@ -37,9 +98,10 @@ strLstGet(const StringList *this, unsigned int listIdx)
}
/***********************************************************************************************************************************
Concatenate a list of strings into a single string using the specified separator
Join a list of strings into a single string using the specified separator
***********************************************************************************************************************************/
String *strLstCat(StringList *this, const char *separator)
String *
strLstJoin(const StringList *this, const char *separator)
{
String *join = strNew("");

View File

@ -15,9 +15,13 @@ typedef struct StringList StringList;
Functions
***********************************************************************************************************************************/
StringList *strLstNew();
StringList *strLstNewSplit(const String *string, const String *delimiter);
StringList *strLstDup(const StringList *sourceList);
StringList *strLstAdd(StringList *this, String *string);
StringList *strLstAddZ(StringList *this, const char *string);
String *strLstGet(const StringList *this, unsigned int listIdx);
String *strLstCat(StringList *this, const char *separator);
String *strLstJoin(const StringList *this, const char *separator);
const char **strLstPtr(const StringList *this);
unsigned int strLstSize(const StringList *this);
void strLstFree(StringList *this);

View File

@ -133,25 +133,34 @@ my $oTestDef =
},
{
&TESTDEF_NAME => 'type-list',
&TESTDEF_TOTAL => 3,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
{
'common/type/list' => TESTDEF_COVERAGE_FULL,
'common/type/stringList' => TESTDEF_COVERAGE_FULL,
},
},
{
&TESTDEF_NAME => 'type-string',
&TESTDEF_TOTAL => 2,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
{
'common/type/list' => TESTDEF_COVERAGE_FULL,
},
},
{
&TESTDEF_NAME => 'type-string',
&TESTDEF_TOTAL => 5,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
{
'common/type/string' => TESTDEF_COVERAGE_FULL,
},
},
{
&TESTDEF_NAME => 'type-string-list',
&TESTDEF_TOTAL => 4,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
{
'common/type/stringList' => TESTDEF_COVERAGE_FULL,
},
},
{
&TESTDEF_NAME => 'encode',
&TESTDEF_TOTAL => 1,

View File

@ -42,67 +42,4 @@ void testRun()
TEST_ERROR(lstGet(list, lstSize(list)), AssertError, "cannot get index 9 from list with 9 value(s)");
}
// *****************************************************************************************************************************
if (testBegin("StringList"))
{
StringList *list = strLstNew();
// Add strings to the list
// -------------------------------------------------------------------------------------------------------------------------
for (int listIdx = 0; listIdx <= LIST_INITIAL_SIZE; listIdx++)
{
if (listIdx == 0)
{
TEST_RESULT_PTR(strLstAdd(list, NULL), list, "add null item");
}
else
TEST_RESULT_PTR(strLstAdd(list, strNewFmt("STR%02d", listIdx)), list, "add item %d", listIdx);
}
TEST_RESULT_INT(strLstSize(list), 9, "list size");
// Read them back and check values
// -------------------------------------------------------------------------------------------------------------------------
for (unsigned int listIdx = 0; listIdx < strLstSize(list); listIdx++)
{
if (listIdx == 0)
{
TEST_RESULT_PTR(strLstGet(list, listIdx), NULL, "check null item");
}
else
TEST_RESULT_STR(strPtr(strLstGet(list, listIdx)), strPtr(strNewFmt("STR%02d", listIdx)), "check item %d", listIdx);
}
// Check pointer
// -------------------------------------------------------------------------------------------------------------------------
const char **szList = strLstPtr(list);
for (unsigned int listIdx = 0; listIdx < strLstSize(list); listIdx++)
{
if (listIdx == 0)
{
TEST_RESULT_PTR(szList[listIdx], NULL, "check null item");
}
else
TEST_RESULT_STR(szList[listIdx], strPtr(strNewFmt("STR%02d", listIdx)), "check item %d", listIdx);
}
// strLstCat()
// -------------------------------------------------------------------------------------------------------------------------
list = strLstNew();
TEST_RESULT_STR(strPtr(strLstCat(list, ", ")), "", "empty list");
strLstAdd(list, strNew("item1"));
strLstAdd(list, strNew("item2"));
TEST_RESULT_STR(strPtr(strLstCat(list, ", ")), "item1, item2", "empty list");
strLstAdd(list, NULL);
TEST_RESULT_STR(strPtr(strLstCat(list, ", ")), "item1, item2, [NULL]", "empty list");
strLstFree(list);
}
}

View File

@ -0,0 +1,104 @@
/***********************************************************************************************************************************
Test String Lists
***********************************************************************************************************************************/
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void testRun()
{
// *****************************************************************************************************************************
if (testBegin("strLstNew(), strLstAdd, strLstGet(), strLstSize(), and strLstFree()"))
{
StringList *list = strLstNew();
// Add strings to the list
// -------------------------------------------------------------------------------------------------------------------------
for (int listIdx = 0; listIdx <= LIST_INITIAL_SIZE; listIdx++)
{
if (listIdx == 0)
{
TEST_RESULT_PTR(strLstAdd(list, NULL), list, "add null item");
}
else
TEST_RESULT_PTR(strLstAdd(list, strNewFmt("STR%02d", listIdx)), list, "add item %d", listIdx);
}
TEST_RESULT_INT(strLstSize(list), 9, "list size");
// Read them back and check values
// -------------------------------------------------------------------------------------------------------------------------
for (unsigned int listIdx = 0; listIdx < strLstSize(list); listIdx++)
{
if (listIdx == 0)
{
TEST_RESULT_PTR(strLstGet(list, listIdx), NULL, "check null item");
}
else
TEST_RESULT_STR(strPtr(strLstGet(list, listIdx)), strPtr(strNewFmt("STR%02d", listIdx)), "check item %d", listIdx);
}
strLstFree(list);
}
// *****************************************************************************************************************************
if (testBegin("strLstPtr()"))
{
StringList *list = strLstNew();
// Add strings to the list
// -------------------------------------------------------------------------------------------------------------------------
for (int listIdx = 0; listIdx <= 3; listIdx++)
{
if (listIdx == 0)
strLstAdd(list, NULL);
else
strLstAdd(list, strNewFmt("STR%02d", listIdx));
}
// Check pointer
// -------------------------------------------------------------------------------------------------------------------------
const char **szList = strLstPtr(list);
for (unsigned int listIdx = 0; listIdx < strLstSize(list); listIdx++)
{
if (listIdx == 0)
{
TEST_RESULT_PTR(szList[listIdx], NULL, "check null item");
}
else
TEST_RESULT_STR(szList[listIdx], strPtr(strNewFmt("STR%02d", listIdx)), "check item %d", listIdx);
}
strLstFree(list);
}
// *****************************************************************************************************************************
if (testBegin("strLstJoin()"))
{
StringList *list = strLstNew();
TEST_RESULT_STR(strPtr(strLstJoin(list, ", ")), "", "empty list");
strLstAdd(list, strNew("item1"));
strLstAddZ(list, "item2");
TEST_RESULT_STR(strPtr(strLstJoin(list, ", ")), "item1, item2", "list");
strLstAdd(list, NULL);
TEST_RESULT_STR(strPtr(strLstJoin(list, ", ")), "item1, item2, [NULL]", "list with NULL at end");
TEST_RESULT_STR(strPtr(strLstJoin(strLstDup(list), ", ")), "item1, item2, [NULL]", "dup'd list will NULL at end");
strLstFree(list);
}
// *****************************************************************************************************************************
if (testBegin("strLstSplit()"))
{
TEST_RESULT_STR(strPtr(strLstJoin(strLstNewSplit(strNew(""), strNew(", ")), ", ")), "", "empty list");
TEST_RESULT_STR(strPtr(strLstJoin(strLstNewSplit(strNew("item1"), strNew(", ")), ", ")), "item1", "one item");
TEST_RESULT_STR(strPtr(strLstJoin(strLstNewSplit(strNew("item1, item2"), strNew(", ")), ", ")), "item1, item2", "two items");
}
}

View File

@ -8,18 +8,24 @@ Test Run
void testRun()
{
// *****************************************************************************************************************************
if (testBegin("strNew() and strFree()"))
if (testBegin("strNew(), strNewSzN(), and strFree()"))
{
String *string = strNew("static string");
TEST_RESULT_STR(strPtr(string), "static string", "new with static string");
TEST_RESULT_INT(strSize(string), 13, "check size");
TEST_RESULT_INT(strlen(strPtr(string)), 13, "check size with strlen()");
TEST_RESULT_CHAR(strPtr(string)[2], 'a', "check character");
strFree(string);
TEST_RESULT_VOID(strFree(string), "free string");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_STR(strPtr(strNewSzN("testmorestring", 4)), "test", "new string with size limit");
// -------------------------------------------------------------------------------------------------------------------------
string = strNewFmt("formatted %s %04d", "string", 1);
TEST_RESULT_STR(strPtr(string), "formatted string 0001", "new with formatted string");
strFree(string);
TEST_RESULT_VOID(strFree(string), "free string");
}
// *****************************************************************************************************************************
@ -39,4 +45,32 @@ void testRun()
strFree(string);
strFree(string2);
}
// *****************************************************************************************************************************
if (testBegin("strDup()"))
{
String *string = strNew("duplicated string");
String *stringDup = strDup(string);
TEST_RESULT_STR(strPtr(stringDup), strPtr(string), "duplicated strings match");
}
// *****************************************************************************************************************************
if (testBegin("strEq()"))
{
TEST_RESULT_BOOL(strEq(strNew("equalstring"), strNew("equalstring")), true, "strings equal");
TEST_RESULT_BOOL(strEq(strNew("astring"), strNew("anotherstring")), false, "strings not equal");
TEST_RESULT_BOOL(strEq(strNew("astring"), strNew("bstring")), false, "equal length strings not equal");
}
// *****************************************************************************************************************************
if (testBegin("strTrim()"))
{
TEST_RESULT_STR(strPtr(strTrim(strNew(""))), "", "trim empty");
TEST_RESULT_STR(strPtr(strTrim(strNew("X"))), "X", "no trim (one char)");
TEST_RESULT_STR(strPtr(strTrim(strNew("no-trim"))), "no-trim", "no trim (string)");
TEST_RESULT_STR(strPtr(strTrim(strNew(" \tbegin-only"))), "begin-only", "trim begin");
TEST_RESULT_STR(strPtr(strTrim(strNew("end-only\t "))), "end-only", "trim end");
TEST_RESULT_STR(strPtr(strTrim(strNew("\n\rboth\r\n"))), "both", "trim both");
TEST_RESULT_STR(strPtr(strTrim(strNew("begin \r\n\tend"))), "begin \r\n\tend", "ignore whitespace in middle");
}
}

View File

@ -39,7 +39,7 @@ void testRun()
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNew("--perl-option=value"));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "perl option");
TEST_RESULT_STR(strPtr(strLstCat(parseData->perlOptionList, ",")), "value", "check perl option");
TEST_RESULT_STR(strPtr(strLstJoin(parseData->perlOptionList, ",")), "value", "check perl option");
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNewFmt("--%s", cfgOptionName(cfgOptDelta)));
@ -108,7 +108,7 @@ void testRun()
strLstAdd(argList, strNew("/path/to/wal/RECOVERYWAL"));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "command arguments");
TEST_RESULT_STR(
strPtr(strLstCat(parseData->commandArgList, "|")), "000000010000000200000003|/path/to/wal/RECOVERYWAL",
strPtr(strLstJoin(parseData->commandArgList, "|")), "000000010000000200000003|/path/to/wal/RECOVERYWAL",
" check command arguments");
// -------------------------------------------------------------------------------------------------------------------------
@ -118,7 +118,7 @@ void testRun()
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "single valid option");
TEST_RESULT_STR(
strPtr(strLstCat(parseData->parseOptionList[cfgOptDbHost].valueList, "|")), "db1.test.org", " check db-host option");
strPtr(strLstJoin(parseData->parseOptionList[cfgOptDbHost].valueList, "|")), "db1.test.org", " check db-host option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
@ -128,7 +128,7 @@ void testRun()
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "multiple valid options");
TEST_RESULT_STR(
strPtr(strLstCat(parseData->parseOptionList[cfgOptLinkMap].valueList, "|")), "ts1=/path/to/ts1|ts2=/path/to/ts2",
strPtr(strLstJoin(parseData->parseOptionList[cfgOptLinkMap].valueList, "|")), "ts1=/path/to/ts1|ts2=/path/to/ts2",
" check link-map options");
}

View File

@ -24,21 +24,21 @@ void testRun()
cmdLineParam[cmdLineParamSize++] = "backup";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', 'backup')|[NULL]", "simple command");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--compress";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', '--compress', 'backup')|[NULL]", "simple option");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--db-host=db1";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', '--compress', '--db1-host', 'db1', 'backup')|[NULL]",
"option with = before value");
@ -47,7 +47,7 @@ void testRun()
cmdLineParam[cmdLineParamSize++] = "postgres";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN
"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', 'backup')|[NULL]",
"option with space before value");
@ -56,7 +56,7 @@ void testRun()
cmdLineParam[cmdLineParamSize++] = "--perl-option=-I.";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|" TEST_PERL_MAIN
" --perl-option=\"-I.\"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', 'backup')|[NULL]",
"perl option");
@ -65,7 +65,7 @@ void testRun()
cmdLineParam[cmdLineParamSize++] = "--no-online";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|" TEST_PERL_MAIN
" --perl-option=\"-I.\"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', '--no-online',"
" 'backup')|[NULL]",
@ -75,7 +75,7 @@ void testRun()
cmdLineParam[cmdLineParamSize++] = "cmdarg1";
TEST_RESULT_STR(
strPtr(strLstCat(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|" TEST_PERL_MAIN
" --perl-option=\"-I.\"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', '--no-online', 'backup',"
" 'cmdarg1')|[NULL]",