source: trunk/src/kShell/kShell.c@ 23

Last change on this file since 23 was 23, checked in by bird, 23 years ago

working on it...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.9 KB
Line 
1/* $Id: kShell.c 23 2002-11-22 04:00:46Z bird $
2 *
3 * kShell - A mini shell.
4 *
5 * Copyright (c) 2002 knut st. osmundsen <bird@anduin.net>
6 *
7 *
8 * This file is part of kBuild.
9 *
10 * kBuild is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published
12 * by the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * kBuild is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with kBuild; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 */
25
26/** @design kShell (Micro Shell)
27 *
28 * The micro shell provides the basic shell functionality kBuild need - no more,
29 * no less. It is intended to be as simple as possible.
30 *
31 * The shell commands are case sensitive - all lowercase.
32 *
33 * The shell environment variables are case sensitive or insensitive according to
34 * host os.
35 *
36 *
37 *
38 * @subsection Command Separators
39 *
40 * There is one command separator '&&'. This works like splitting the command line
41 * into several makefile lines. This splitting isn't done by the micro shell but
42 * the makefile interpreter.
43 *
44 * You might thing this is limiting, but no, you can use all the makefile command
45 * prefixes.
46 *
47 *
48 *
49 * @subsection Path Component Separator (/)
50 *
51 * The shell uses '/' as path component separator.
52 * For host OSes with the notion of drive letters or similar, ':' is
53 * used to separate the drive letter and the path.
54 *
55 *
56 *
57 * @subsection UNC paths
58 *
59 * For host OSes which supports UNC paths these are supported but for the chdir
60 * command.
61 *
62 * The Path Component Separator is still '/' for UNC paths.
63 *
64 *
65 *
66 * @subsection Wildchars
67 *
68 * '*' and '?' are accepted as wildchars.
69 *
70 * '*' means 0 or more characters. <br>
71 * '?' means 1 character.
72 *
73 * When the term 'pattern' is use in command description this means that
74 * wildchars are accepted.
75 *
76 *
77 *
78 * @subsection Quoting
79 *
80 * Use double quotes (") to quote filenames or executables containing spaces.
81 *
82 *
83 *
84 * @subsection Execute Program
85 *
86 * If the first, possibly quoted, word of a commandline if not found as an
87 * internal command will be tried executed. If no path it will be searched
88 * for in the PATH environment variable.
89 *
90 *
91 *
92 * @subsection Commands
93 *
94 * This section will describe the commands implemented by the shell.
95 *
96 *
97 *
98 * @subsubsection copy
99 * Copies one or more files to a target file or directory.
100 *
101 * <b>Syntax: copy <source file pattern> [more sources] <target> </b>
102 *
103 * Specify one or more source file patterns.
104 *
105 * Specify exactly one target. The target may be a directory or a file.
106 * If it's a file and multiple source files specified either thru pattern or
107 * multiple source file specifications, the target file will be a copy of the
108 * last one.
109 *
110 * The command fails if a source file isn't found. It also fails on read or
111 * write errors.
112 *
113 *
114 *
115 * @subsubsection copytree
116 * Copies one or more files to a target file or directory.
117 *
118 * <b>Syntax: copytree <source directory> <target directory> </b>
119 *
120 * Specify exactly one source directory.
121 *
122 * Specify exactly one target directory. The target directory path will be
123 * created if doesn't exist.
124 *
125 * The command fails if source directory isn't found. It also fails on read or
126 * write errors.
127 *
128 *
129 *
130 * @subsubsection rm
131 * Deletes one or more files.
132 *
133 * <b>Syntax: rm [file pattern] [more files] </b>
134 *
135 * Specify 0 or more file patterns for deletion.
136 *
137 * This command fails if it cannot delete a file. It will not fail if a file
138 * doesn't exist. It will neither fail if no files are specified.
139 *
140 *
141 *
142 * @subsubsection rmtree
143 * Deletes one or more directory trees.
144 *
145 * <b>Syntax: rmtree [directory pattern] [directories] </b>
146 *
147 * Specify 0 or more directory patterns for deletion.
148 *
149 * This command fails if it cannot delete a file or directory. It will not fail
150 * if a directory doesn't exist. It will neither fail if no files are specified.
151 *
152 *
153 *
154 * @subsubsection chdir
155 * Changes the current directory.
156 *
157 * This updates the .CWD macro to the new current directory path.
158 *
159 * <b>Syntax: chdir <directory> </b>
160 *
161 *
162 *
163 * @subsubsection mkdir
164 * Create directory.
165 *
166 * <b>Syntax: mkdir <directory> </b>
167 *
168 * Specify one directory to create.
169 *
170 *
171 *
172 * @subsubsection rmdir
173 * Remove directory.
174 *
175 * <b>Syntax: rmdir <directory> </b>
176 *
177 * Specify one directory to remove. The directory must be empty.
178 *
179 * This command failes if directory isn't empty. It will not fail if
180 * the directory doesn't exist.
181 *
182 *
183 *
184 * @subsubsection set
185 * Set environment variable.
186 *
187 * <b>Syntax: set <envvar>=<value> </b>
188 *
189 *
190 *
191 * @subsubsection unset
192 * Unset enviornment variable(s).
193 *
194 * <b>Syntax: unset <envvar pattern> [more envvars] </b>
195 *
196 * Specify on or more environment variable patterns.
197 *
198 *
199 *
200 * @subsubsection pushenv
201 * Pushes a set of environment variables onto the environment stack. The
202 * variables can later be popped back using the popenv command.
203 *
204 * If '*' is specified as pattern the complete enviornment is pushed and
205 * when popped it will <b>replace</b> the enviornment.
206 *
207 * <b>Syntax: pushenv <envvar pattern> [more envvars] </b>
208 * <b>Syntax: pushenv * </b>
209 *
210 *
211 *
212 * @subsubsection popenv
213 * Pop a set of environment variables from the environment stack. If a '*'
214 * push was done, we'll replace the enviornment with the variables poped off
215 * the stack.
216 *
217 * <b>Syntax: popenv </b>
218 *
219 *
220 *
221 */
222
223
224/*******************************************************************************
225* Defined Constants And Macros *
226*******************************************************************************/
227#define KSWORD_FLAGS_ESCAPE 1
228#define KSWORD_FLAGS_QUOTED 2
229#define KSWORD_FLAGS_PATTERN 4
230
231#define KSHELL_MAX_COMMAND 4096
232
233/**
234 * Test if this is an escapable character or not.
235 */
236#define KSHELL_ESCAPABLE(ch) ( (ch) == '"' \
237 || (ch) == '\'' \
238 || (ch) == '`' \
239 || (ch) == 'ï' \
240 )
241
242/**
243 * Test if this is a quote character or not.
244 */
245#define KSHELL_QUOTE(ch) ( (ch) == '"' \
246 || (ch) == '\'' \
247 || (ch) == '`' \
248 || (ch) == 'ï' \
249 )
250
251/**
252 * Test if this is a wildchar character or not.
253 */
254#define KSHELL_WILDCHAR(ch) ( (ch) == '*' || (ch) == '?' )
255
256
257
258/*******************************************************************************
259* Header Files *
260*******************************************************************************/
261#include "kShell.h"
262#include <kLib/kLib.h>
263#include <kLib/kString.h>
264
265#include <string.h>
266#include <stdlib.h>
267#include <stdio.h>
268
269
270/*******************************************************************************
271* Global Variables *
272*******************************************************************************/
273typedef struct _kshellWord
274{
275 int fFlags;
276 char * pszWord;
277 unsigned cchWord;
278 const char *pszWordOrg;
279 unsigned cchWordOrg;
280} KSHELLWORD, *PKSHELLWORD;
281
282
283typedef struct _kshellWords
284{
285 unsigned cWords;
286 KSHELLWORD aWords[1];
287} KSHELLWORDS, *PKSHELLWORDS;
288
289
290/*******************************************************************************
291* Global Variables *
292*******************************************************************************/
293static const char *pszkshellCurDir = NULL;
294
295/*******************************************************************************
296* Internal Functions *
297*******************************************************************************/
298PKSHELLWORDS kshellWordsParse(const char *pszText, int cWords, PKSHELLWORDS pPrevWords);
299void kshellWordsDestroy(PKSHELLWORDS pWords);
300
301int kshellSyntaxError(const char *pszCmd, const char *pszMessage);
302int kshellError(const char *pszCmd, const char *pszMessage);
303
304int kshellCmd_copy(const char *pszCmd, PKSHELLWORDS pWords);
305int kshellCmd_copytree(const char *pszCmd, PKSHELLWORDS pWords);
306int kshellCmd_sync(const char *pszCmd, PKSHELLWORDS pWords);
307int kshellCmd_synctree(const char *pszCmd, PKSHELLWORDS pWords);
308int kshellCmd_rm(const char *pszCmd, PKSHELLWORDS pWords);
309int kshellCmd_rmtree(const char *pszCmd, PKSHELLWORDS pWords);
310int kshellCmd_chdir(const char *pszCmd, PKSHELLWORDS pWords);
311int kshellCmd_mkdir(const char *pszCmd, PKSHELLWORDS pWords);
312int kshellCmd_rmdir(const char *pszCmd, PKSHELLWORDS pWords);
313int kshellCmd_set(const char *pszCmd, PKSHELLWORDS pWords);
314int kshellCmd_unset(const char *pszCmd, PKSHELLWORDS pWords);
315int kshellCmd_pushenv(const char *pszCmd, PKSHELLWORDS pWords);
316int kshellCmd_popenv(const char *pszCmd, PKSHELLWORDS pWords);
317int kshellCmd_echo(const char *pszCmd, PKSHELLWORDS pWords);
318int kshellCmd_write(const char *pszCmd, PKSHELLWORDS pWords);
319int kshellCmd_ExecuteProgram(const char *pszCmd, PKSHELLWORDS pWords);
320
321
322
323/**
324 * Initiate the shell.
325 * Allow us to initiate globals.
326 *
327 * @returns 0 on success.
328 * @returns error code on error.
329 * @param fVerbose If set banner will be printed.
330 */
331int kshellInit(int fVerbose)
332{
333 if (fVerbose)
334 {
335 printf("\n"
336 "kShell v0.0.0\n"
337 "Copyright 2002 knut st. osmundsen <bird@anduin.net>\n"
338 "\n");
339 }
340 return 0;
341}
342
343
344/**
345 * Terminate the shell.
346 * Allow us to cleanup stuff.
347 */
348void kshellTerm(void)
349{
350 return;
351}
352
353
354/**
355 * Start interactive shell interface.
356 * This reads commands from stdin till 'exit' is encountered or end-of-file.
357 *
358 * @returns returncode of last command.
359 */
360int kshellInteractive(void)
361{
362 static char szCmd[KSHELL_MAX_COMMAND];
363 int rc = 0;
364
365 printf("kShell>");
366 fflush(stdout);
367 while (fgets(&szCmd[0], sizeof(szCmd), stdin))
368 {
369 char *pszEnd = &szCmd[strlen(&szCmd[0]) - 1];
370 while (pszEnd >= &szCmd[0] && (*pszEnd == '\n' || *pszEnd == '\r'))
371 *pszEnd-- = '\0';
372
373 if (!strcmp(&szCmd[0], "exit"))
374 break;
375
376 rc = kshellExecute(&szCmd[0]);
377 printf("kShell(rc=%d)>", rc);
378 fflush(stdout);
379 }
380
381 return rc;
382}
383
384/**
385 * Execute a shell command.
386 * @returns 0 on success if command.
387 * @returns Error code if command failed.
388 * @returns Return code from program.
389 * @returns 1742 (KSHELL_ERROR_PROGRAM_NOT_FOUND) if program wasn't found.
390 * @returns 1743 (KSHELL_ERROR_COMMAND_TOO_LONG) if program commandline was too long.
391 * @param pszCmd Command or program to execute.
392 */
393int kshellExecute(const char *pszCmd)
394{
395#define MAX_WORDS (~0)
396 static struct _kshellCommands
397 {
398 const char *pszCmd;
399 unsigned cWords; /* Number of words including the command it self. */
400 int (*pfnCmd)(const char *, PKSHELLWORDS);
401 } aCmds[] =
402 {
403 {"copy", MAX_WORDS, kshellCmd_copy},
404 {"copytree", 3, kshellCmd_copytree},
405 {"sync", MAX_WORDS, kshellCmd_sync},
406 {"synctree", 3, kshellCmd_synctree},
407 {"rm", MAX_WORDS, kshellCmd_rm},
408 {"rmtree", MAX_WORDS, kshellCmd_rmtree},
409 {"chdir", 2, kshellCmd_chdir},
410 {"mkdir", MAX_WORDS, kshellCmd_mkdir},
411 {"rmdir", MAX_WORDS, kshellCmd_rmdir},
412 {"set", 1, kshellCmd_set},
413 {"unset", MAX_WORDS, kshellCmd_unset},
414 {"pushenv", MAX_WORDS, kshellCmd_pushenv},
415 {"popenv", 1, kshellCmd_popenv},
416 {"echo", 2, kshellCmd_echo},
417 {"write", 2, kshellCmd_write},
418
419 /* last entry */
420 {"", 1, kshellCmd_ExecuteProgram}
421 };
422#undef MAX_WORDS
423
424 PKSHELLWORDS pWords;
425 int i;
426 int rc;
427
428
429 /*
430 * Parse out the first word.
431 */
432 pWords = kshellWordsParse(pszCmd, 1, NULL);
433 if (!pWords)
434 return KSHELL_ERROR_NOT_ENOUGHT_MEMORY;
435 if (!pWords->cWords)
436 return 0;
437
438
439 /*
440 * Look for command.
441 * Note! the last entry is the default command (execute program).
442 */
443 for (i = 0; i < (sizeof(aCmds) / sizeof(aCmds[0])) - 1; i++)
444 {
445 if (!strcmp(aCmds[i].pszCmd, pWords->aWords[0].pszWord))
446 break;
447 }
448
449
450 /*
451 * Execute command.
452 */
453 if (aCmds[i].cWords > 1)
454 {
455 pWords = kshellWordsParse(pszCmd, aCmds[i].cWords, pWords);
456 if (!pWords)
457 return KSHELL_ERROR_NOT_ENOUGHT_MEMORY;
458 }
459 rc = aCmds[i].pfnCmd(pszCmd, pWords);
460
461
462 return rc;
463}
464
465
466/**
467 * Parses words out of a string.
468 *
469 * @returns Pointer to a words structure.
470 * This must be destroy calling kshellWordsDestroy().
471 * @returns NULL on failure (out of memory).
472 * @param pszText Text string to parse.
473 * @param cWords Number of words to parse. Will stop after cWords.
474 * @param pPrevWords Pointer to structur of previosly parse words from pszText.
475 * Use this to continue parsing a string. The pPrevWords
476 * structure will be destroyed.
477 * If NULL we'll start from the begining.
478 */
479PKSHELLWORDS kshellWordsParse(const char *pszText, int cWords, PKSHELLWORDS pPrevWords)
480{
481 PKSHELLWORDS pWords = pPrevWords;
482
483 /*
484 * If previous work done, skip to end of that.
485 */
486 if (pPrevWords && pPrevWords->cWords)
487 {
488 pszText = pPrevWords->aWords[pPrevWords->cWords - 1].pszWordOrg
489 + pPrevWords->aWords[pPrevWords->cWords - 1].cchWordOrg;
490 cWords -= pPrevWords->cWords;
491 }
492
493 /*
494 * Parse loop
495 */
496 while (cWords-- > 0)
497 {
498 KSHELLWORD word = {0,0,0,0,0};
499 char chEnd = ' ';
500 char ch;
501
502 /*
503 * Skip blanks to find start of word.
504 */
505 while (*pszText == ' ' || *pszText == '\t')
506 pszText++;
507 if (!*pszText)
508 break;
509 word.pszWordOrg = pszText;
510
511
512 /*
513 * Quoted?
514 * Any possible quote!
515 */
516 if (KSHELL_QUOTE(*pszText))
517 {
518 chEnd = *pszText++;
519 word.fFlags |= KSWORD_FLAGS_QUOTED;
520 }
521
522
523 /*
524 * Find end of word and look for escape and pattern characters.
525 * We escape by doubling the character, not by slashing!
526 */
527 while ((ch = *pszText) != '\0' && (ch != chEnd || pszText[1] == chEnd))
528 {
529 if (ch == pszText[1] && KSHELL_ESCAPABLE(ch))
530 {
531 word.fFlags |= KSWORD_FLAGS_ESCAPE;
532 pszText++;
533 }
534 if (KSHELL_WILDCHAR(ch))
535 word.fFlags |= KSWORD_FLAGS_PATTERN;
536 pszText++;
537 }
538 if (word.fFlags & KSWORD_FLAGS_QUOTED)
539 pszText++;
540 word.cchWordOrg = pszText - word.pszWordOrg;
541
542
543 /*
544 * Make a copy of the word and unescape (if needed).
545 */
546 word.pszWord = malloc(word.cchWordOrg + 1);
547 if (!word.pszWord)
548 {
549 kshellWordsDestroy(pWords);
550 return NULL;
551 }
552
553 if (word.fFlags & KSWORD_FLAGS_ESCAPE)
554 {
555 int cch = word.cchWordOrg;
556 const char *pszSrc = word.pszWordOrg;
557 char * pszTrg = word.pszWord;
558 while (cch)
559 {
560 char ch;
561 if ((ch = *pszSrc) == pszSrc[1] && KSHELL_ESCAPABLE(ch))
562 pszSrc++;
563 *pszTrg++ = ch;
564 pszSrc++;
565 }
566 word.cchWord = pszTrg - word.pszWord;
567 *pszTrg = '\0';
568 }
569 else
570 {
571 if (word.fFlags & KSWORD_FLAGS_QUOTED)
572 {
573 word.cchWord = word.cchWordOrg - 2;
574 memcpy(word.pszWord, word.pszWordOrg + 1, word.cchWord);
575 }
576 else
577 {
578 word.cchWord = word.cchWordOrg;
579 memcpy(word.pszWord, word.pszWordOrg, word.cchWord);
580 }
581 word.pszWord[word.cchWord] = '\0';
582 }
583
584
585 /*
586 * Add to words structure.
587 */
588 if (!pWords || ((pWords->cWords + 1) % 32))
589 {
590 void *pv = realloc(pWords, sizeof(KSHELLWORDS) + ((pWords ? pWords->cWords : 0) + 32) * sizeof(KSHELLWORD));
591 if (!pv)
592 {
593 kshellWordsDestroy(pWords);
594 return NULL;
595 }
596 if (pWords)
597 pWords = pv;
598 else
599 {
600 pWords = pv;
601 pWords->cWords = 0;
602 }
603 }
604 pWords->aWords[pWords->cWords++] = word;
605 }
606
607 return pWords;
608}
609
610
611/**
612 * Destroys a words structure freeing it's memory.
613 * @param pWords Pointer to words structure to destroy.
614 */
615void kshellWordsDestroy(PKSHELLWORDS pWords)
616{
617 if (pWords)
618 {
619 int i;
620
621 for (i = 0; i < pWords->cWords; i++)
622 {
623 if (pWords->aWords[i].pszWord)
624 {
625 free(pWords->aWords[i].pszWord);
626 pWords->aWords[i].pszWord = NULL;
627 }
628 }
629
630 pWords->cWords = 0;
631 free(pWords);
632 }
633}
634
635
636/**
637 * Display an syntax message.
638 * @returns KSHELL_ERROR_SYNTAX_ERROR
639 * @param pszCmd The command name.
640 * @param pszMessage Message text.
641 */
642int kshellSyntaxError(const char *pszCmd, const char *pszMessage)
643{
644 fflush(stdout);
645 fprintf(stderr, "Syntax error while executing command '%s': %s\n", pszCmd, pszMessage);
646 return KSHELL_ERROR_SYNTAX_ERROR;
647}
648
649
650/**
651 * Display an generic message.
652 * @returns KSHELL_ERROR_SYNTAX_ERROR
653 * @param pszCmd The command name.
654 * @param pszMessage Message text.
655 */
656int kshellError(const char *pszCmd, const char *pszMessage)
657{
658 fflush(stdout);
659 fprintf(stderr, "Error while executing command '%s': %s\n", pszCmd, pszMessage);
660 return -1;
661}
662
663
664/**
665 * Execute program.
666 *
667 * @returns program return code.
668 * @returns 1742 (KSHELL_ERROR_PROGRAM_NOT_FOUND) if program wasn't found.
669 * @returns 1743 (KSHELL_ERROR_COMMAND_TOO_LONG) if program commandline was too long.
670 *
671 * @param pszCmd Pointer to commandline.
672 * @param pWords Pointer to 1st word in pszCmd.
673 */
674int kshellCmd_ExecuteProgram(const char *pszCmd, PKSHELLWORDS pWords)
675{
676 return -1;
677}
678
679
680/*
681 *
682 * The commands are documented externally.
683 * (Bad idea btw!)
684 *
685 */
686
687
688int kshellCmd_copy(const char *pszCmd, PKSHELLWORDS pWords)
689{
690 return -1;
691}
692
693
694int kshellCmd_copytree(const char *pszCmd, PKSHELLWORDS pWords)
695{
696 return -1;
697}
698
699
700int kshellCmd_sync(const char *pszCmd, PKSHELLWORDS pWords)
701{
702 return -1;
703}
704
705
706int kshellCmd_synctree(const char *pszCmd, PKSHELLWORDS pWords)
707{
708 return -1;
709}
710
711
712int kshellCmd_rm(const char *pszCmd, PKSHELLWORDS pWords)
713{
714 return -1;
715}
716
717
718int kshellCmd_rmtree(const char *pszCmd, PKSHELLWORDS pWords)
719{
720 return -1;
721}
722
723
724int kshellCmd_chdir(const char *pszCmd, PKSHELLWORDS pWords)
725{
726 return -1;
727}
728
729
730int kshellCmd_mkdir(const char *pszCmd, PKSHELLWORDS pWords)
731{
732 return -1;
733}
734
735
736int kshellCmd_rmdir(const char *pszCmd, PKSHELLWORDS pWords)
737{
738 return -1;
739}
740
741
742int kshellCmd_set(const char *pszCmd, PKSHELLWORDS pWords)
743{
744 return -1;
745}
746
747
748int kshellCmd_unset(const char *pszCmd, PKSHELLWORDS pWords)
749{
750 return -1;
751}
752
753
754int kshellCmd_pushenv(const char *pszCmd, PKSHELLWORDS pWords)
755{
756 return -1;
757}
758
759
760int kshellCmd_popenv(const char *pszCmd, PKSHELLWORDS pWords)
761{
762 return -1;
763}
764
765
766/** @subsubsection echo
767 * Prints a message to stdout.
768 *
769 * <b>Syntax: echo <level> <message>
770 *
771 * Level is verbosity level of the message. This is compared with the
772 * KBUILD_MSG_LEVEL environment variable. The message is suppressed if the
773 * level is lower that KBUILD_MSG_LEVEL.
774 *
775 * The message is printed word for word normalize with a single space between
776 * the words. It's therefore a good thing to quote the message.
777 *
778 * The message can be empty. Then a blank line will be printed.
779 */
780int kshellCmd_echo(const char *pszCmd, PKSHELLWORDS pWords)
781{
782 int rc = KSHELL_ERROR_SYNTAX_ERROR;
783
784 /*
785 * Get the message level from the message.
786 */
787 if (pWords->cWords >= 2)
788 {
789 unsigned uMsgLevel = kStrToUnsigned(pWords->aWords[1].pszWord, -2);
790 if (uMsgLevel != -2)
791 {
792 if (uMsgLevel <= kEnvGetUnsigned("KBUILD_MSG_LEVEL", 0))
793 {
794 /* output all the words forcing one space separation */
795 pWords = kshellWordsParse(pszCmd, -1, pWords);
796 if (pWords)
797 {
798 int i;
799 for (i = 2; i < pWords->cWords; i++)
800 fwrite(pWords->aWords[i].pszWord, pWords->aWords[i].cchWord, 1, stdout);
801 }
802
803 /* new line */
804 fputc('\n', stdout);
805 fflush(stdout);
806 }
807 }
808 else
809 kshellSyntaxError("echo", "invalid message level!");
810 }
811 else
812 kshellSyntaxError("echo", "requires at least one argument!");
813
814 return -1;
815}
816
817
818int kshellCmd_write(const char *pszCmd, PKSHELLWORDS pWords)
819{
820 return -1;
821}
822
823
824
Note: See TracBrowser for help on using the repository browser.