/*@Header@*/ /* $Id: shquote.c,v 1.3 2008/09/25 17:33:43 ksb Exp $ * $Compile: ${cc:-cc} -DTEST -o %F %f */ #include #include #include #include #include #include "machine.h" extern char *progname; /*@Explode out@*/ /* Output a string such that the shell get exactly 1 word (ksb) * that is exactly that string. * * Compute the shortest quote via: * with \ we need 1 for every: " ' \ ~ ` # $ & ; | * ( ) [ ] ? > < { * and $IFS * { and ~ for csh mostly, ~ is only magic after IFS, really * = is magic under -k in the shell, sigh. * with ' we need 2 + 3 for every: ' * with " we need 2 + 1 for every: $ ` \ " * * The best could change in the string, so a Human can do better. * N.B. nits we don't cost perfectly: * - the last $ in a "word" is not backslashed, * - the ~ is only special as the first character in a word. */ void OutQuote(char *pcOut) { register int fQ, iC, i, fPrefQ; register char *pcScan; static char *pcIFS = (char *)0; auto int aiSum[1<<(8*sizeof(char))]; auto int iBacks, iDoubles, iSingles; static char acMagic[] = "\"\'\\`#$&;|*()[?><{~="; static char acDoubleDown[] = "\"$`\\"; if ((char *)0 == pcIFS && (char *)0 == (pcIFS = getenv("IFS"))) { pcIFS = " \t\n"; } for (i = 0; i < sizeof(aiSum)/sizeof(aiSum[0]); ++i) { aiSum[i] = 0; } iBacks = 0; for (pcScan = pcOut; '\000' != (iC = *pcScan); ++pcScan) { ++aiSum[iC]; iBacks += (char *)0 != strchr(pcIFS, iC); } for (i = 0; '\000' != (iC = acMagic[i]); ++i) { iBacks += aiSum[iC]; } iDoubles = 2 + (aiSum['$'] + aiSum['`'] + aiSum['\\'] + aiSum['\"']); iSingles = 2 + 3*aiSum['\'']; if (0 == iBacks) { printf("%s", pcOut); return; } if (iSingles <= iBacks && 2 == iSingles) { printf("\'%s\'", pcOut); return; } if (iDoubles <= iBacks && 2 == iDoubles) { printf("\"%s\"", pcOut); return; } #if DEBUG_SHQ printf("back %d; single %d; double %d\n", iBacks, iSingles, iDoubles); #endif /* No cheap way out, take the (least) hard way */ if (iBacks <= iDoubles && iBacks <= iSingles) { fQ = fPrefQ = '\000'; } else if (iDoubles < iSingles) { fQ = fPrefQ = '\"'; } else { fQ = fPrefQ = '\''; } if ('\000' != fQ) { printf("%c", fQ); } for (pcScan = pcOut; '\000' != (iC = *pcScan); ++pcScan) { switch (fQ) { case '\000': if ((char *)0 == strchr(acMagic, iC) && (char *)0 == strchr(pcIFS, iC)) { break; } if ('\\' == iC && '\\' == pcScan[1]) { printf("\'"); fQ = '\''; } else if ('\000' == fPrefQ || fPrefQ == iC) { printf("\\"); } else { printf("%c", fPrefQ); fQ = fPrefQ; } break; case '\'': if ('\'' != iC) { break; } printf("'\\"); fQ = '\000'; break; case '\"': if ('\"' == iC && '\000' == pcScan[1]) { printf("\"\\"); fQ = '\000'; break; } if ((char *)0 == strchr(acDoubleDown, iC)) { break; } /* "...$" preserves the dollar sign, strangely */ if ('$' == iC && '\000' == pcScan[1]) { break; } printf("\\"); break; default: fprintf(stderr, "%s: quote code snark!\n", progname); break; } printf("%c", iC); } if ('\000' != fQ) { printf("%c", fQ); } } /*@Remove@*/ #if TEST #include char *progname = "shquote-test"; static char *apcTest[] = { "simple", "white space", "doublequote\"", "singlequote\'", "backslash\\", "dollarsign$", "tailspace ", "\"\"\"\"", "''''", "\\\\\\\\", "$$$$", "ham&eggs", "either|or", "a?b:c;", "-k A=3", "\"\'\\`#$&;|*()[?><{~=", "\"stuff we want in 'singles' kinda\"", (char *)0 }; int main(int argc, char **argv, char **envp) { register char **ppc; for (ppc = 1 == argc ? apcTest : argv+1; (char *)0 != *ppc; ++ppc) { printf("%s => ", *ppc); OutQuote(*ppc); printf("\n"); } exit(EX_OK); } /*@Remove@*/ #endif