In einem String ersetzen

lano

Aktives Mitglied
Moin.

Ich würde gern meinem Programm ein Template (sacht man da so?) als Argument mit geben.
Nu bin ich da noch nicht soo weit...
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void replace_var(char *arg, char *var, char *value) {}

int main(int argc, char *argv[], char *envp[]) {

  char *arg;
  char *var = "Hallo Welt";

  arg = malloc(sizeof(char) * (strlen(argv[1]) + 1));
  if (!arg) {
    printf("Error: malloc failed....\n");
    exit(1);
  }

  if (strcpy(arg, argv[1]) != NULL) {
    /* Vorher: echo $test */
    printf("arg: %s \n", arg);

    // Nur hier damit hier was steht...
    replace_var(arg, "$test", var);

    /* Nachher: echo Hallo Welt */
    printf("arg: %s \n", arg);

  } else {
    printf("Error: strcpy failed....\n");
  }

  free(arg);
  arg = NULL;

  return EXIT_SUCCESS;
}

Jemand eine Idee wie man da sinnig vorgehen könnte?
 
Zuletzt bearbeitet:
Gleich vorweg: Diese Lösung ist weder elegant, noch fertig, noch fehlerfrei.

Als alter VB6-Programmierer wollte ich mal meine geliebten Funktionen wie Split(), Replace(), Join() etc. nachbauen. Noch im Codingboard hat @german mir dazu mal als Basis diese Split()-Funktion geschrieben:

C:
int SplitAlloc(const char *const str, const char *const delims, const int mergeDelims, const size_t maxTok, char ***const pTokens, size_t *const pTokCount) //Quelle: https://www.coding-board.de/resources/c-split-funktion-string-an-trennzeichen-in-einen-vector-von-teilstrings-zerlegen.42/
{
    size_t len = 0;
    char *pChar = NULL;
    char *pOrigin = NULL;
    char **ppChar = NULL;

    if (pTokens) *pTokens = NULL;
    if (pTokCount) *pTokCount = 0;

    if (!str || !delims || mergeDelims < 0 || mergeDelims > 2 || !maxTok || !pTokens || !pTokCount || !(*pTokens = (char**) malloc(2 * sizeof(char*)))) return 1;

    if (!(**pTokens = pOrigin = (char*)malloc((len = strlen(str) + 1))))
    {
        free(*pTokens);
        *pTokens = NULL;
        return 1;
    }

    pChar = strncpy(**pTokens, str, len);
    *pTokCount = 1;
    (*pTokens)[1] = pOrigin;

    char *(__cdecl *search_fn)(const char *, const char *) = mergeDelims == 2 ? strstr : strpbrk;
    size_t delim_len = mergeDelims == 2 ? strlen(delims) : 1u;

    while (*pTokCount < maxTok && ((pChar = search_fn(pChar, delims))))
    {
        *pChar = 0;

        char *next = pChar + delim_len;
        if (mergeDelims && (*pTokens)[*pTokCount - 1] == pChar) (*pTokens)[*pTokCount - 1] = pChar = next;
            else if (mergeDelims && (*next == 0 || search_fn(next, delims) == next))
            pChar = next;
        else
        {
            if (!(ppChar = (char**)realloc(*pTokens, (*pTokCount + 2) * sizeof(char*))))
            {
                free(pOrigin);
                free(*pTokens);
                *pTokens = NULL;
                *pTokCount = 0;
                return 1;
            }

            *pTokens = ppChar;
            (*pTokens)[(*pTokCount)++] = pChar = next;
            (*pTokens)[*pTokCount] = pOrigin;
        }
    }

    return 0;
}

Dazu gehört noch das hier; aufzurufen, wenn man fertig ist:

C:
void SplitFree(char ***const pTokens, const size_t tokCount)
{
    if (*pTokens)
    {
        if (**pTokens && tokCount) free((*pTokens)[tokCount]);

        free(*pTokens);
        *pTokens = NULL;
    }
}

Nun, als erstes hier Join(), das ist eine Funktion, die ein String-Array (also ein Array von char-Arrays) erwartet und diese zu einem String zusammenklebt, wobei noch an den "Schnittstellen" immer ein bestimmter String eingefügt wird:

C:
char *Join(char **StrArr, char *Expression, unsigned int Number)
{
    int i;
    char *Temp;
    int CharNum = 0;
    int CurrPos = 0;

    Temp = (char*) calloc(1, sizeof(char));
    if (Temp == NULL) exit(EXIT_FAILURE);

    if (StrArr == NULL) return(Temp);
    if (Expression == NULL) return(Temp);
    if (Number == 0) return(Temp);
    if (strlen(Expression) == 0) return(Temp);
  
    for (i = 0; i < Number; i++)
    {
        CharNum = CharNum + strlen(StrArr[i]);
    }
    CharNum = CharNum + ((Number - 1) * strlen(Expression));
  
    free(Temp);
    Temp = (char*) calloc(CharNum + 1, sizeof(char));
    if (Temp == NULL) exit(EXIT_FAILURE);
  
    for (i = 0; i < Number; i++)
    {
        memcpy(&Temp[CurrPos], StrArr[i], strlen(StrArr[i]));
        CurrPos = CurrPos + strlen(StrArr[i]);
      
        if (i < Number - 1) memcpy(&Temp[CurrPos], Expression, strlen(Expression));
        CurrPos = CurrPos + strlen(Expression);
    }
  
    return(Temp);
}

Die Idee ist nun, für ein Replace() den Ausgangsstring erstmal an den Stellen des zu ersetzenden Strings mit Split() zu zerschneiden, dann die Teilstrings, die die zu ersetzenden Strings enthalten, durch den Ersetzungsstring zu ersetzen und dann alles wieder zusammen zu "join"en. Das funktioniert auch, allerdings gibt's noch Probleme, wenn der Ersetzungsstring ganz am Anfang oder am Ende oder mehrfach hintereinander oder so auftaucht, ich weiß nicht mehr genau. Kannst es ja mal ausprobieren, hier die Replace-Funktion, das ist dann jetzt nicht mehr viel:

C:
char *Replace(char *Expression, char *FindString, char *ReplaceString)
{
    int retVal;
    char **Temp;
    char *Res;
    unsigned int Number;

    if (Expression == NULL) return(Expression);
    if (strlen(Expression) == 0) return(Expression);
    if (FindString == NULL) return(Expression);
    if (strlen(FindString) == 0) return(Expression);
    if (ReplaceString == NULL) return(Expression);
    if (InStr(Expression, FindString) == 0) return(Expression);

    retVal = SplitAlloc(Expression, FindString, 2, -1, &Temp, &Number);
    Res = Join(Temp, ReplaceString, Number);

    SplitFree(&Temp, Number);
  
    return(Res);
}
 
Zuletzt bearbeitet:
@BAGZZlash Ja die alten Schätzchen habe ich noch auf meiner TODO. Wollte das Speichermanagement noch etwas anpassen, bevor ich das wieder als Ressource einstelle. Auch so ein alter Knochen wie ich, hat in der Zwischenzeit wieder dazugelernt … ;)

@lano Ist die Ausgabe zum stdout letztlich das was du brauchst, oder benötigst du tatsächlich einen veränderten String im Programmcode? Falls ersteres, würde ich nämlich körperlich gar keine Ersetzung vornehmen und einfach bei jedem Erscheinen des übergebenen Arguments den Ersatzstring ausgeben. Sollte mit strstr in einer Schleife relativ simpel umzusetzen sein.
 
Probier mal:
C:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/** \brief Search all occurences of a substring and replace them
* \param src        Original string.
* \param search     The substring that will be replaced.
* \param repl       The string to replace the search string with.
* \param lengthPtr  Pointer to a variable of type size_t that receives the new length. (can be NULL)
* \return Allocated buffer containing the updated string. NULL if the function failed to allocate memory. */
char *Replace(const char *const src, const char *const search, const char *const repl, size_t *const lengthPtr);

int main(void)
{
  size_t length = 0;
  char *updated = Replace("xxx bbb xxx ccc xxx", "xxx", "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", &length);
  if (updated)
  {
    printf("String:\n%s\n\nLaenge:\n%u\n", updated, (unsigned)length);
    free(updated);
    return 0;
  }

  return 1;
}

char *Replace(const char *const src, const char *const search, const char *const repl, size_t *const lengthPtr)
{
  if (!src || !search || !repl)
    return NULL;

  const size_t src_len = strlen(src), search_len = strlen(search), repl_len = strlen(repl);
  size_t capacity = src_len > repl_len ? src_len * 2 : repl_len * 2;
  char *buf = malloc(capacity);
  if (!buf)
    return NULL;

  const char *src_it = src;
  const char *found = NULL;
  char *buf_it = buf;
  size_t current_len = 0;
  for (;;)
  {
    found = strstr(src_it, search);
    if (!found)
    {
      const size_t total_len = current_len + strlen(src_it) + 1U;
      if (total_len > capacity)
      {
        char *tmp = realloc(buf, total_len);
        if (!tmp)
        {
          free(buf);
          return NULL;
        }

        buf = tmp;
        buf_it = buf + current_len;
      }

      strcpy(buf_it, src_it);
      if (lengthPtr)
        *lengthPtr = total_len - 1U;

      break;
    }

    const size_t dif_len = (size_t)(found - src_it);
    if (current_len + dif_len + repl_len + 1U > capacity)
    {
      char *tmp = realloc(buf, (capacity *= 2));
      if (!tmp)
      {
        free(buf);
        return NULL;
      }

      buf = tmp;
      buf_it = buf + current_len;
    }

    buf_it = strncpy(buf_it, src_it, dif_len) + dif_len;
    buf_it = strcpy(buf_it, repl) + repl_len;
    src_it = found + search_len;
    current_len += dif_len + repl_len;
  }

  return buf;
}
Wenn dir die neue Länge egal ist, übergib NULL an den letzten Parameter. Ich hatte sie bloß sowieso in der Funktion ermittelt ...

// EDIT: Bugfix für 2x src Länge < repl Länge
 
Zuletzt bearbeitet:
Hab mich für weniger Obst entschieden...

C:
char* itoa(int value, int base){
    static char buffer[32] = {0};
    int i = 30;
    for(; value && i ; --i, value /= base)
    buffer[i] = "0123456789abcdef"[value % base];
return &buffer[i+1];
}

Kann doch nicht sein das es dafür keine Funktion gibt...
 
Die itoa Funktion ist zwar keine Standardfunktion, aber sie ist Quasi-Standard (anders gesagt, die Wahrscheinlichkeit dass sie nicht implementiert ist, ist gering). Schau mal in die stdlib.h.
Ich habe aber langsam das Gefühl, dass wir hier über ein XY Problem reden. Sind denn hier nicht Funktionen wie sprintf für dein Problemchen besser geeignet? Ja, es ist dann nicht String $wasweissich der ersetzt wird, sondern der Formatbezeichner %s oder %d oder …, aber ist das wirklich relevant?
 
Sind denn hier nicht Funktionen wie sprintf für dein Problemchen besser geeignet?

Find ich schon optisch bedenklich :rolleyes::LOL:

Ja, es ist dann nicht String $wasweissich der ersetzt wird, sondern der Formatbezeichner %s oder %d oder …, aber ist das wirklich relevant?

Ich will meinem Programm nen cmd Aufruf mitgeben. ala ./prog --exec 'echo Hallo deine Fritzbox hat die MAC $fb_serial'
oder ./prog --exec 'echo Hallo bla.. $fb_serial blub.. $fb_version'

Ich wüsste jetzt nicht wie ich da mit printf bei gehn könnte. Aber bevor du dir die Mühe machst. Ich find das so schöner. Auch wenn das nicht optimal sein mag. es reicht mir.
 
Nee ich meine schon sprinf und nicht printf wenn du dir einen String zusammen bauen willst (wobei dein Kommentar oben schon wieder darauf hin deutet, dass du ja trotzdem nur eine Ausgabe zum stdout haben willst, hmmm…). Wenn die Reihenfolge der Daten immer gleich ist, sollte das funktionieren. Du kannst ja zig mal %s und zig mal %d im string haben, wenn Typ und Reihenfolge der nachfolgenden Argumente passt. Aber ohne Glaskugelmodus kann ich immer nur den Bruchteil überblicken, den du hier postest.
 
Okay. Wie sieht dann dein Konzept aus? Holst du dir vorab alle Daten, auch die die du nicht brauchst? In dem Fall könntest du auch gleich noch die Konvertierung zu strings vornehmen für alles was als int reinkommt. Dann hast du im Nachgang nicht das Problem, je nach $... pattern entscheiden zu müssen, ob du konvertieren musst oder nicht. Mag dann zwar auch Daten konvertieren die der User nicht anfordert, aber vermutlich ist das zu verkraften und der einfachere Weg.
 
Wenn du unbedingt C verwenden willst ist es nun einmal so, dass C minimal ist. Das war nie dafür gedacht, riesige Userspace-Programme darin zu schreiben, oder komfortablen text-IO, ohne zusätzliche Bibliotheken zu nutzen.
Dafür linkt man in nichttrivialen Projekten dann halt die glib dazu, oder eine äquivalente Bibliothek. Bevor du nun selbst komischen Kram bastelst solltest du dir das mal ansehen.

mfg,
Lowl3v3l
 
Hehe, da hast du Recht. Macht mir zwar Spaß sowas selbst zu implementieren. Aber klar, das ist so wie ein bereits rundes Rad, eckig neu zu erfinden. Die ganzen Bugfixes sind bei einer Lib schon gelaufen, während auch mein bereits upgedateter Code oben sich noch in eine Endlosschleife verheddert wenn search die Länge 0 hat...
 
Zuletzt bearbeitet:
Zurück
Oben Unten