Permalänk
Medlem

C# Dela upp sträng

Jag håller på med ett litet program i C# som bl.a. skickar ssh commands till min linux server, för att förenkla några för mig ständigt återkommande uppgifter.

Ett av kommandona returnerar en sträng med hur lång tid det är kvar på en filöverföring, problemet är att den kan se lite olika ut.

Exempel: 10h20m5s 2m30s 1h2m 9s

Hur ska jag dela upp den "snyggast"? Nedan är vad jag åstadkommit hittills, det funkar men jag är övertygad om att man kan göra det snyggare med mindre kod?

Jag vill alltså att den alltid ska se ut som HH:MM:SS eller MM:SS om H == 00

string s = "0", m = "0", h = "0"; // Ersätt h och m med :, ta bort s if (eta.Contains("h")) eta = eta.Replace('h', ':'); if (eta.Contains("m")) eta = eta.Replace('m', ':'); if (eta.Contains("s")) eta = eta.Remove(eta.IndexOf("s")); // Räkna hur många : det finns i strängen int count = eta.Count(x => x == ':'); if (count == 2) // h:m:s { s = eta.Substring(eta.LastIndexOf(":") + 1); eta = eta.Remove(eta.LastIndexOf(":")); m = eta.Substring(eta.LastIndexOf(":") + 1); h = eta.Remove(eta.LastIndexOf(":")); } else if (count == 1) // m:s { s = eta.Substring(eta.LastIndexOf(":") + 1); m = eta.Remove(eta.LastIndexOf(":")); } else // bara sekunder kvar s = eta; // Lägg till en nolla framför om det är ensiffrigt if (h.Length < 2) h = "0" + h; if (m.Length < 2) m = "0" + m; if (s.Length < 2) s = "0" + s; eta = h != "00" ? $"{h}:{m}:{s}" : $"{m}:{s}";

Visa signatur

AMD Ryzen 7 7800X3D • ASUS TUF Gaming B650-Plus WiFi • Noctua NH-D15
XFX Radeon RX 6950 XT Speedster MERC 319 • MSI Optix MAG271CQR • Dell UltraSharp U2515H
G.Skill 32GB DDR5 6000MHz CL30 • WD Black SN750 NVMe SSD 1 TB • Crucial P3 Plus NVMe SSD 1 TB
Phanteks P600S • ASUS TUF Gaming 850W Gold • Logitech Craft Keyboard • Logitech MX Master 3

Permalänk
Medlem
Skrivet av immutable:

Jag håller på med ett litet program i C# som bl.a. skickar ssh commands till min linux server, för att förenkla några för mig ständigt återkommande uppgifter.

Ett av kommandona returnerar en sträng med hur lång tid det är kvar på en filöverföring, problemet är att den kan se lite olika ut.

Exempel: 10h20m5s 2m30s 1h2m 9s

Hur ska jag dela upp den "snyggast"? Nedan är vad jag åstadkommit hittills, det funkar men jag är övertygad om att man kan göra det snyggare med mindre kod?
https://i.imgur.com/x5KsgVK.png
Jag vill alltså att den alltid ska se ut som HH:MM:SS eller MM:SS om H == 00

string s = "0", m = "0", h = "0"; // Ersätt h och m med :, ta bort s if (eta.Contains("h")) eta = eta.Replace('h', ':'); if (eta.Contains("m")) eta = eta.Replace('m', ':'); if (eta.Contains("s")) eta = eta.Remove(eta.IndexOf("s")); // Räkna hur många : det finns i strängen int count = eta.Count(x => x == ':'); if (count == 2) // h:m:s { s = eta.Substring(eta.LastIndexOf(":") + 1); eta = eta.Remove(eta.LastIndexOf(":")); m = eta.Substring(eta.LastIndexOf(":") + 1); h = eta.Remove(eta.LastIndexOf(":")); } else if (count == 1) // m:s { s = eta.Substring(eta.LastIndexOf(":") + 1); m = eta.Remove(eta.LastIndexOf(":")); } else // bara sekunder kvar s = eta; // Lägg till en nolla framför om det är ensiffrigt if (h.Length < 2) h = "0" + h; if (m.Length < 2) m = "0" + m; if (s.Length < 2) s = "0" + s; eta = h != "00" ? $"{h}:{m}:{s}" : $"{m}:{s}";

Jag skulle nog satsat på att se till att TimeSpan förstår din input och sedan nyttja dess funktionalitet för att formatera hur du nu vill formatera.

Nu har jag inte gått in i detalj på exakt vad du vill ha, bara sett till att den förstår den typen av format du måste tolka.
Se nedan för en illustration av vad jag menar (du kan passa in formatsträngar till ToString() också, om du vill justera vad den spottar ur sig):

var inputs = new [] { "10h20m5s", "2m30s", "1h2m", "9s" }; var formats = new [] { "h\\hm\\ms\\s", "m\\ms\\s", "h\\hm\\m", "s\\s" }; foreach (var input in inputs) { var span = TimeSpan.ParseExact(input, formats, System.Globalization.CultureInfo.InvariantCulture); Console.WriteLine(span); }

ger då alltså med standardformateringen som TimeSpan gör vid konvertering till sträng:

10:20:05 00:02:30 01:02:00 00:00:09

Visa signatur

Desktop: Ryzen 5800X3D || MSI X570S Edge Max Wifi || Sapphire Pulse RX 7900 XTX || Gskill Trident Z 3600 64GB || Kingston KC3000 2TB || Samsung 970 EVO Plus 2TB || Samsung 960 Pro 1TB || Fractal Torrent || Asus PG42UQ 4K OLED
Proxmox server: Ryzen 5900X || Asrock Rack X570D4I-2T || Kingston 64GB ECC || WD Red SN700 1TB || Blandning av WD Red / Seagate Ironwolf för lagring || Fractal Node 304

Permalänk
Medlem

@evil penguin: Ja jäklar, det var ju betydligt lättare och snyggare! Har aldrig använt TimeSpan innan så stort tack för tipset, det kommer jag ha mycket nytta av

Visa signatur

AMD Ryzen 7 7800X3D • ASUS TUF Gaming B650-Plus WiFi • Noctua NH-D15
XFX Radeon RX 6950 XT Speedster MERC 319 • MSI Optix MAG271CQR • Dell UltraSharp U2515H
G.Skill 32GB DDR5 6000MHz CL30 • WD Black SN750 NVMe SSD 1 TB • Crucial P3 Plus NVMe SSD 1 TB
Phanteks P600S • ASUS TUF Gaming 850W Gold • Logitech Craft Keyboard • Logitech MX Master 3

Permalänk
Medlem
Skrivet av immutable:

@evil penguin: Ja jäklar, det var ju betydligt lättare och snyggare! Har aldrig använt TimeSpan innan så stort tack för tipset, det kommer jag ha mycket nytta av

En annan stor fördel med att använda en TimeSpan är att den faktiskt representerar den angivna mängden tid, så om du vill räkna på något så är den utmärkt för det (till skillnad från en sträng).

Om du t.ex. vill se vad klockan skulle vara när det är klart så kan du addera din TimeSpan till DateTime.Now.

Visa signatur

Desktop: Ryzen 5800X3D || MSI X570S Edge Max Wifi || Sapphire Pulse RX 7900 XTX || Gskill Trident Z 3600 64GB || Kingston KC3000 2TB || Samsung 970 EVO Plus 2TB || Samsung 960 Pro 1TB || Fractal Torrent || Asus PG42UQ 4K OLED
Proxmox server: Ryzen 5900X || Asrock Rack X570D4I-2T || Kingston 64GB ECC || WD Red SN700 1TB || Blandning av WD Red / Seagate Ironwolf för lagring || Fractal Node 304

Permalänk
Medlem

@evil penguin:

Tack för ännu ett värdefullt tips! Nu har jag lagt till en "Estimated finish time" i mitt program också

Visa signatur

AMD Ryzen 7 7800X3D • ASUS TUF Gaming B650-Plus WiFi • Noctua NH-D15
XFX Radeon RX 6950 XT Speedster MERC 319 • MSI Optix MAG271CQR • Dell UltraSharp U2515H
G.Skill 32GB DDR5 6000MHz CL30 • WD Black SN750 NVMe SSD 1 TB • Crucial P3 Plus NVMe SSD 1 TB
Phanteks P600S • ASUS TUF Gaming 850W Gold • Logitech Craft Keyboard • Logitech MX Master 3

Permalänk
Medlem

@evil penguin: Du verkar ju vara duktig på det här med strängar Vad tror du om följande kod, går den att "snygga" till på något fiffigt sätt?

Andra personer får självklart komma med förslag också

Jag avskyr att ha massa if-satser efter varandra i koden, men jag är rätt ny på programmering och försöker lära mig så mycket det går på egen hand. Kommer förhoppningsvis in på en utbildning i höst.

Anledningen att jag måste ha massa if-satser är att jag får exceptions annars när jag använder s.IndexOF().

Jag har självklart all kod nedan i en if(s.StartsWith("Transferred:") && s.Contains("ETA")) -sats, men det händer att det saknas nåt i strängen och då kraschar programmet. Detta händer sällan så det är svårt att felsöka exakt när det kraschar, men med nedan if-satser så har det funkat länge nu, peppar peppar.

Ett tag körde jag allt nedan i en try-catch sats och det funkar så klart också, men jag har förstått att det är "bad practice" att göra så?

Alla tips är välkomna.

// Exempel på hur inkommande sträng från rclone ser ut // Transferred: 227.500M / 5.752 GBytes, 4%, 43.724 MBytes/s, ETA 2m9s string total = String.Empty; string speed = String.Empty; string percent = String.Empty; string eta = String.Empty; //Ta bort från början till första tabben och ta bort alla mellanslag s = s.Remove(0, s.IndexOf("\t") + 1); s = s.Replace(" ", ""); // 227.500M/5.752GBytes,4%,43.724MBytes/s,ETA2m9s if (s.Contains("A") && s.Contains("B")) { eta = s.Substring(s.LastIndexOf("A") + 1); // eta = 2m9s s = s.Remove(s.LastIndexOf("B")); } // 227.500M/5.752GBytes,4%,43.724M if (s.Contains(",") && s.Contains("%")) { speed = s.Substring(s.LastIndexOf(",") + 1); // speed = 43.724M s = s.Remove(s.LastIndexOf("%")); } // 227.500M/5.752GBytes,4 if (s.Contains(",") && s.Contains("B")) { percent = s.Substring(s.LastIndexOf(",") + 1); // percent = 4 s = s.Remove(s.LastIndexOf("B")); } // 227.500M/5.752G if (s.Contains("/")) { total = s.Substring(s.LastIndexOf("/") + 1); // total = 5.752G s = s.Remove(s.LastIndexOf("/")); // s = 227.500M } s = Functions.TrimNumber(s); total = Functions.TrimNumber(total); speed = Functions.TrimNumber(speed, "speed"); // Försök konvertera till int int.TryParse(percent, out int x); // Om x är mellan 0-100, uppdatera progressbaren if (x > 0 && x <= 100) worker.ReportProgress(x, $"{s}|{total}|{speed}|{percent}|{eta}");

public static string TrimNumber(string num, string typ = "") { if (num == "") return "0"; string unit = ""; if (num.Contains(".")) num = num.Replace(".", ","); if (num.Contains("K")) unit = "K"; else if (num.Contains("M")) unit = "M"; else if (num.Contains("G")) unit = "G"; num = num.Remove(num.IndexOf(unit)); double.TryParse(num, out double c); num = c.ToString("0.00"); num = (typ == "speed") ? num + $" {unit}B/s" : num + $" {unit}B "; return num.Trim(); }

Visa signatur

AMD Ryzen 7 7800X3D • ASUS TUF Gaming B650-Plus WiFi • Noctua NH-D15
XFX Radeon RX 6950 XT Speedster MERC 319 • MSI Optix MAG271CQR • Dell UltraSharp U2515H
G.Skill 32GB DDR5 6000MHz CL30 • WD Black SN750 NVMe SSD 1 TB • Crucial P3 Plus NVMe SSD 1 TB
Phanteks P600S • ASUS TUF Gaming 850W Gold • Logitech Craft Keyboard • Logitech MX Master 3

Permalänk
Medlem
Skrivet av immutable:

@evil penguin: Du verkar ju vara duktig på det här med strängar Vad tror du om följande kod, går den att "snygga" till på något fiffigt sätt?

Andra personer får självklart komma med förslag också

Jag avskyr att ha massa if-satser efter varandra i koden, men jag är rätt ny på programmering och försöker lära mig så mycket det går på egen hand. Kommer förhoppningsvis in på en utbildning i höst.

Anledningen att jag måste ha massa if-satser är att jag får exceptions annars när jag använder s.IndexOF().

Jag har självklart all kod nedan i en if(s.StartsWith("Transferred:") && s.Contains("ETA")) -sats, men det händer att det saknas nåt i strängen och då kraschar programmet. Detta händer sällan så det är svårt att felsöka exakt när det kraschar, men med nedan if-satser så har det funkat länge nu, peppar peppar.

Ett tag körde jag allt nedan i en try-catch sats och det funkar så klart också, men jag har förstått att det är "bad practice" att göra så?

Alla tips är välkomna.

// Exempel på hur inkommande sträng från rclone ser ut // Transferred: 227.500M / 5.752 GBytes, 4%, 43.724 MBytes/s, ETA 2m9s string total = String.Empty; string speed = String.Empty; string percent = String.Empty; string eta = String.Empty; //Ta bort från början till första tabben och ta bort alla mellanslag s = s.Remove(0, s.IndexOf("\t") + 1); s = s.Replace(" ", ""); // 227.500M/5.752GBytes,4%,43.724MBytes/s,ETA2m9s if (s.Contains("A") && s.Contains("B")) { eta = s.Substring(s.LastIndexOf("A") + 1); // eta = 2m9s s = s.Remove(s.LastIndexOf("B")); } // 227.500M/5.752GBytes,4%,43.724M if (s.Contains(",") && s.Contains("%")) { speed = s.Substring(s.LastIndexOf(",") + 1); // speed = 43.724M s = s.Remove(s.LastIndexOf("%")); } // 227.500M/5.752GBytes,4 if (s.Contains(",") && s.Contains("B")) { percent = s.Substring(s.LastIndexOf(",") + 1); // percent = 4 s = s.Remove(s.LastIndexOf("B")); } // 227.500M/5.752G if (s.Contains("/")) { total = s.Substring(s.LastIndexOf("/") + 1); // total = 5.752G s = s.Remove(s.LastIndexOf("/")); // s = 227.500M } s = Functions.TrimNumber(s); total = Functions.TrimNumber(total); speed = Functions.TrimNumber(speed, "speed"); // Försök konvertera till int int.TryParse(percent, out int x); // Om x är mellan 0-100, uppdatera progressbaren if (x > 0 && x <= 100) worker.ReportProgress(x, $"{s}|{total}|{speed}|{percent}|{eta}");

public static string TrimNumber(string num, string typ = "") { if (num == "") return "0"; string unit = ""; if (num.Contains(".")) num = num.Replace(".", ","); if (num.Contains("K")) unit = "K"; else if (num.Contains("M")) unit = "M"; else if (num.Contains("G")) unit = "G"; num = num.Remove(num.IndexOf(unit)); double.TryParse(num, out double c); num = c.ToString("0.00"); num = (typ == "speed") ? num + $" {unit}B/s" : num + $" {unit}B "; return num.Trim(); }

Ett första intryck är att det nog blir extra spretigt att leta efter lite alla möjliga enstaka tecken ("A", "B", ",", "%", ...) för att hitta vad som är vad. För mig känns det iaf besvärligt att följa...

Jag tror att jag skulle föreslå en splitt på "," (kommatecken) och att sedan passa in de olika delarna till någon funktion för relevant parsning av just det värde som ska finnas på den positionen.

Visa signatur

Desktop: Ryzen 5800X3D || MSI X570S Edge Max Wifi || Sapphire Pulse RX 7900 XTX || Gskill Trident Z 3600 64GB || Kingston KC3000 2TB || Samsung 970 EVO Plus 2TB || Samsung 960 Pro 1TB || Fractal Torrent || Asus PG42UQ 4K OLED
Proxmox server: Ryzen 5900X || Asrock Rack X570D4I-2T || Kingston 64GB ECC || WD Red SN700 1TB || Blandning av WD Red / Seagate Ironwolf för lagring || Fractal Node 304

Permalänk
Medlem
Skrivet av immutable:

@evil penguin: Du verkar ju vara duktig på det här med strängar Vad tror du om följande kod, går den att "snygga" till på något fiffigt sätt?

Andra personer får självklart komma med förslag också

Jag avskyr att ha massa if-satser efter varandra i koden, men jag är rätt ny på programmering och försöker lära mig så mycket det går på egen hand. Kommer förhoppningsvis in på en utbildning i höst.

Anledningen att jag måste ha massa if-satser är att jag får exceptions annars när jag använder s.IndexOF().

Jag har självklart all kod nedan i en if(s.StartsWith("Transferred:") && s.Contains("ETA")) -sats, men det händer att det saknas nåt i strängen och då kraschar programmet. Detta händer sällan så det är svårt att felsöka exakt när det kraschar, men med nedan if-satser så har det funkat länge nu, peppar peppar.

Ett tag körde jag allt nedan i en try-catch sats och det funkar så klart också, men jag har förstått att det är "bad practice" att göra så?

Alla tips är välkomna.

// Exempel på hur inkommande sträng från rclone ser ut // Transferred: 227.500M / 5.752 GBytes, 4%, 43.724 MBytes/s, ETA 2m9s string total = String.Empty; string speed = String.Empty; string percent = String.Empty; string eta = String.Empty; //Ta bort från början till första tabben och ta bort alla mellanslag s = s.Remove(0, s.IndexOf("\t") + 1); s = s.Replace(" ", ""); // 227.500M/5.752GBytes,4%,43.724MBytes/s,ETA2m9s if (s.Contains("A") && s.Contains("B")) { eta = s.Substring(s.LastIndexOf("A") + 1); // eta = 2m9s s = s.Remove(s.LastIndexOf("B")); } // 227.500M/5.752GBytes,4%,43.724M if (s.Contains(",") && s.Contains("%")) { speed = s.Substring(s.LastIndexOf(",") + 1); // speed = 43.724M s = s.Remove(s.LastIndexOf("%")); } // 227.500M/5.752GBytes,4 if (s.Contains(",") && s.Contains("B")) { percent = s.Substring(s.LastIndexOf(",") + 1); // percent = 4 s = s.Remove(s.LastIndexOf("B")); } // 227.500M/5.752G if (s.Contains("/")) { total = s.Substring(s.LastIndexOf("/") + 1); // total = 5.752G s = s.Remove(s.LastIndexOf("/")); // s = 227.500M } s = Functions.TrimNumber(s); total = Functions.TrimNumber(total); speed = Functions.TrimNumber(speed, "speed"); // Försök konvertera till int int.TryParse(percent, out int x); // Om x är mellan 0-100, uppdatera progressbaren if (x > 0 && x <= 100) worker.ReportProgress(x, $"{s}|{total}|{speed}|{percent}|{eta}");

public static string TrimNumber(string num, string typ = "") { if (num == "") return "0"; string unit = ""; if (num.Contains(".")) num = num.Replace(".", ","); if (num.Contains("K")) unit = "K"; else if (num.Contains("M")) unit = "M"; else if (num.Contains("G")) unit = "G"; num = num.Remove(num.IndexOf(unit)); double.TryParse(num, out double c); num = c.ToString("0.00"); num = (typ == "speed") ? num + $" {unit}B/s" : num + $" {unit}B "; return num.Trim(); }

Beror på vad som eventuellt kan saknas, det kan ha stor betydelse i hur man gör sin algoritm. Förväntas man att vissa saker kan saknas och hur ska man då hantera dessa? Enbart returnera en tom sträng eller ska något annat göras?

Permalänk
Medlem

Kör en switch statement istället för alla de där if satserna så blir det mer läsbart

Permalänk
Hedersmedlem

Prova med RegEx. Detta är genererat med ett litet händigt program som heter Expresso.

using System.Text.RegularExpressions; /// <summary> /// Regular expression built for C# on: mån, maj 11, 2020, 11:06:54 /// Using Expresso Version: 3.1.6224, http://www.ultrapico.com /// /// A description of the regular expression: /// /// Transferred:\s+ /// Transferred: /// Whitespace, one or more repetitions /// [Transferred]: A named capture group. [[\d\x2E]*] /// Any character in this class: [\d\x2E], any number of repetitions /// [TransferredUnit]: A named capture group. [.] /// Any character /// Any character in this class: [\s/], one or more repetitions /// [Total]: A named capture group. [[\d\x2E]*] /// Any character in this class: [\d\x2E], any number of repetitions /// Whitespace, any number of repetitions /// [TotalUnit]: A named capture group. [.] /// Any character /// Bytes,\s* /// Bytes, /// Whitespace, any number of repetitions /// [Percent]: A named capture group. [[\d]*] /// Any character in this class: [\d], any number of repetitions /// %,\s* /// %, /// Whitespace, any number of repetitions /// [Speed]: A named capture group. [[\d\x2E]*] /// Any character in this class: [\d\x2E], any number of repetitions /// Whitespace, any number of repetitions /// [SpeedUnit]: A named capture group. [.] /// Any character /// Bytes/s,\s*ETA\s* /// Bytes/s, /// Whitespace, any number of repetitions /// ETA /// Whitespace, any number of repetitions /// [ETA]: A named capture group. [\w*] /// Alphanumeric, any number of repetitions /// /// /// </summary> public static Regex regex = new Regex( "Transferred:\\s+(?<Transferred>[\\d\\x2E]*)(?<TransferredUnit>.)[\\s/]+(?<Total>[\\d\\x2E]*)\\s*(?<TotalUnit>.)Bytes,\\s*(?<Percent>[\\d]*)%,\\s*(?<Speed>[\\d\\x2E]*)\\s*(?<SpeedUnit>.)Bytes/s,\\s*ETA\\s*(?<ETA>\\w*)", RegexOptions.CultureInvariant | RegexOptions.Compiled ); // Capture all Matches in the InputText MatchCollection ms = regex.Matches(InputText);

Visa signatur

Använd gilla för att markera nyttiga inlägg!

Permalänk
Medlem

@giplet , tack regex verkar riktigt bra. Ska testa efter jobbet

Visa signatur

AMD Ryzen 7 7800X3D • ASUS TUF Gaming B650-Plus WiFi • Noctua NH-D15
XFX Radeon RX 6950 XT Speedster MERC 319 • MSI Optix MAG271CQR • Dell UltraSharp U2515H
G.Skill 32GB DDR5 6000MHz CL30 • WD Black SN750 NVMe SSD 1 TB • Crucial P3 Plus NVMe SSD 1 TB
Phanteks P600S • ASUS TUF Gaming 850W Gold • Logitech Craft Keyboard • Logitech MX Master 3

Permalänk
Medlem

@giplet: Jag försökte med RegEx men jag fick det inte att fungera som jag ville, och jag förstod noll av syntaxen så det blev för svårt för mig att felsöka tyvärr. Men det känns som det kan vara ett riktigt kraftfullt verktyg om man lär sig det!

Jag körde på @evil penguin's förslag istället.
Split är också ett väldigt bra verktyg som jag totalt missat innan, så tack för det!

Efter lite googling så ser min kod ut så här nu, blev betydligt mer lättläst än innan.

if (s != null && s.Contains("Transferred:") && s.Contains("ETA") && !s.StartsWith("\b\b\b")) { // Exempel på hur inkommande sträng (s) från rclone ser ut // Transferred: 227.500M / 5.752 GBytes, 4%, 43.724 MBytes/s, ETA 2m9s //Ta bort från början till första tabben och ta bort alla mellanslag s = s.Remove(0, s.IndexOf("\t")); s = s.Replace(" ", ""); // Splitta strängen på följande tecken string[] separatingStrings = { "/s", ",", "%", "/", "ETA" }; string[] split = s.Split(separatingStrings, System.StringSplitOptions.RemoveEmptyEntries); // Så här ser det ut efter Splitten // split[0] = 227.500M // split[1] = 5.752GBytes // split[2] = 4 // split[3] = 43.724MBytes // split[4] = 2m9s // Försök konvertera till int int.TryParse(split[2], out int x); // Om x är mellan 0-100, uppdatera progressbaren if (x >= 0 && x <= 100) worker.ReportProgress(x, split); }

Visa signatur

AMD Ryzen 7 7800X3D • ASUS TUF Gaming B650-Plus WiFi • Noctua NH-D15
XFX Radeon RX 6950 XT Speedster MERC 319 • MSI Optix MAG271CQR • Dell UltraSharp U2515H
G.Skill 32GB DDR5 6000MHz CL30 • WD Black SN750 NVMe SSD 1 TB • Crucial P3 Plus NVMe SSD 1 TB
Phanteks P600S • ASUS TUF Gaming 850W Gold • Logitech Craft Keyboard • Logitech MX Master 3