Ein Problem mit string
-
Klappt:
// siehe edit
Hab ich noch etwas übersehen? Danke
Ich verstehe das nicht ... Ich übersetze einmal für Linux und einmal für Windows. Wenn ich
./a.out
aufrufe, ist alles gut; wenn ich hingegen.\a.exe
aufrufe(in Windows), erhalte ich keine Ausgabe ...#include <filesystem> #include <iostream> #include <set> #include <string> #include <vector> namespace fs = std::filesystem; int main(int argc, char *argv[]) { std::set<char> allowed1; for (char c = '0'; c <= '9'; c++) { allowed1.insert(c); } for (char c = 'a'; c <= 'z'; c++) { allowed1.insert(c); } for (char c = 'A'; c <= 'Z'; c++) { allowed1.insert(c); } allowed1.insert('['); allowed1.insert(']'); allowed1.insert('-'); allowed1.insert('_'); std::vector<std::string> vec; std::string path = fs::current_path().u8string(); for (const auto &entry : fs::directory_iterator(path)) { auto p1 = entry.path().filename().u8string(); auto p2 = entry.path().filename().u8string(); auto last = p2.find_last_of("."); for (int i = 0; i < last; i++) { if (allowed1.count(p2[i]) == 0) { p2[i] = '_'; } } vec.push_back(p1); vec.push_back(p2); } for (int i = 0; i < vec.size(); i += 2) { if (vec[i] != vec[i + 1]) { const auto p1 = vec[i].c_str(); const auto p2 = vec[i + 1].c_str(); std::cout << p1; std::cout << " -> "; std::cout << p2; std::cout << "\n\n"; // std::rename(p1, p2); } } return 0; }
c++ rename1.cpp
x86_64-w64-mingw32-g++ rename1.cpp
Weshalb funktioniert's nicht in Windows?
-
Uppps, ich hab
-static -static-libgcc -static-libstdc++
ganz vergessen, und mitx86_64-w64-mingw32-g++ -static -static-libgcc -static-libstdc++ rename1.cpp
funktioniert es.
Die Win 11-Konsole hatte einfach keine Fehlermeldung, dass dll's fehlen, ausgegeben.
Danke an euch alle.
-
@Fragender sagte in Ein Problem mit string:
Die Win 11-Konsole hatte einfach keine Fehlermeldung, dass dll's fehlen, ausgegeben.
Ja, das ist mich auch schon öfter negativ aufgefallen. Oft gibt es jedoch ein Fehler-Fenster, das zumindest die erste nicht gefundene DLL meldet. Ich vermute das hängt damit zusammen, ob es sich um eine Konsolen- oder eine GUI-Anwendung handelt (GCC+Clang
-mconsole
/ MSVC-subsystem:console
vs. GCC+Clang-mwindows
/ MSVC-subsystem:windows
). Bei programmatisch geladenen DLLs (die nicht explizit gelinkt wurden, z.B. viadlopen
/LoadLibrary
) hat man das auch öfter (wenn nicht im Programmcode gehandhabt). Auch DLLs können schonmal andere DLLs "manuell" laden, da wundert man sich manchmal, dass bei der ersten DLL eine Fehlermeldung kommt, aber für weitere benötigte DLLs keine mehr.
-
Danke @Finnegan , es funktioniert ja jetzt erst mal... und ich kann damit Dateien umbenennen, die komische Zeichen enthalten (sodass beide Systeme damit umgehen können sollten).
Wo sind eigentlich die Unterschiede zwischen
auto
const auto
undconst auto &
?
-
@Fragender sagte in Ein Problem mit string:
Wo sind eigentlich die Unterschiede zwischen
auto
const auto
undconst auto &
?Simple Antwort:
auto
verwendet die selben Deduktions-Regeln wie für Template-Parameter, wobei Referenzen undconst
/volatile
nicht mit abgeleitet werden. Wenn der resultierende Typconst
oder eben eine Referenz sein soll, dann fügt man das so an dasauto
, genau so wie an einen reinen Typ wie z.B.int
. Wenn ich eine Referenz auf einen konstantenint
will, dann schreibe ich auchconst int&
.Es gibt auch noch
decltype(auto)
, wobei die Regeln fürdecltype(<Ausdruck>)
angewendet und Referenzen wie auchconst
/volatile
mit übernommen werden. Das braucht man aber eher selten, im Zweifel ist ein einfachesauto
das, was man will.
-
Eiii, das ist ja die reinste char-/string-Hölle. Ich habe es jetzt noch einmal umgeschrieben, und
- ich kann es mit
x86_64-w64-mingw32-g++ -static rename1.cpp
ohne Fehler kompilieren, - es scheint mit allen Dateinamen in Windows zu funktionieren.
Ob es allerdings "gut"/ "schön"/ "richtig" ist, das ist noch einmal eine ganz andere Frage:
#include <filesystem> #include <iostream> #include <set> #include <string> #include <vector> namespace fs = std::filesystem; int main(int argc, char *argv[]) { std::set<char16_t> allowed1; for (char c = '0'; c <= '9'; c++) { allowed1.insert(c); } for (char c = 'a'; c <= 'z'; c++) { allowed1.insert(c); } for (char c = 'A'; c <= 'Z'; c++) { allowed1.insert(c); } allowed1.insert('('); allowed1.insert(')'); // allowed1.insert('['); // allowed1.insert(']'); allowed1.insert('-'); allowed1.insert('_'); std::vector<std::u16string> vec; auto path = fs::current_path(); for (const auto &entry : fs::directory_iterator(path)) { auto p1 = entry.path().filename().u16string(); auto p2 = entry.path().filename().u16string(); auto last = p2.find_last_of(u"."); for (int i = 0; i < last; i++) { if (p2[i] == '&') { p2[i] = 'n'; } if (allowed1.count(p2[i]) == 0) { p2[i] = '_'; } } for (int i = 0; i < p2.size() - 1; i++) { if (p2[i] == '_' && p2[i + 1] == '_') { p2.erase(i, 1); i--; } } vec.push_back(p1); vec.push_back(p2); } for (int i = 0; i < vec.size(); i += 2) { const auto p1 = vec[i]; const auto p2 = vec[i + 1]; if (p1 != p2) { for (const auto &c : p1) std::cout << static_cast<char>(c); std::cout << " -> "; for (const auto &c : p2) std::cout << static_cast<char>(c); std::cout << "\n"; fs::rename(p1, p2); } } return 0; }
- ich kann es mit
-
@Fragender sagte in Ein Problem mit string:
Eiii, das ist ja die reinste char-/string-Hölle. Ich habe es jetzt noch einmal umgeschrieben, und
- ich kann es mit
x86_64-w64-mingw32-g++ -static rename1.cpp
ohne Fehler kompilieren, - es scheint mit allen Dateinamen in Windows zu funktionieren.
Probier auch mal
ԺДヤغΪրά൬ཛ.txt
. Ich weiss nicht, was du genau vorhast, aber in einem UTF-16-Codierten String via Indizes rumzufummeln ist erstmal auffällig. ich hoffe du weisst was du tust. Willst du alle nicht erlaubten Zeichen aus dem Dateinamen löschen?Ob es allerdings "gut"/ "schön"/ "richtig" ist, das ist noch einmal eine ganz andere Frage:
Allerdings. Da gibts schon ein paar Dinge anzumerken:
std::set<char16_t> allowed1;
Ein
std::set
ist für die Query, einstd::unordered_set
nur und daher wahrscheinlich eine bessere Wahl...allowed1.insert(...); ... for (int i = 0; i < last; i++) { ... if (allowed1.count(p2[i]) == 0) ...
... besonders, wenn du auch noch ein
std::set::count
für jedes Zeichen machst. Verwende lieberstd::(unordered_)set::find
oderstd::(unordered_)set::contains
(ab C++20), dann kommt nicht noch ein linerarer Aufwand bezüglich der Anzahl der Elemente obendrauf.Der Code schreit allerdings nahezu nach einem simplen
std::regex_replace
(siehe unten). Vielleicht solltest du mal schauen, ob das nicht eventuell eine bessere Alternative ist - vorausgesetzt deine Implementierung kann mit UTF-8/16 umgehen.auto last = p2.find_last_of(u"."); for (int i = 0; i < last; i++) { if (p2[i] == '&') { p2[i] = 'n'; } ...
Nicht jeder Dateiname hat einen Punkt! Dieser Code läuft bei einer Datei
dateiname
mit einem Out-of-Bounds-Read und potentiell Write vor die Wand. Behandle den Fall, dassstd::u16string::find_last_of
das gesuchte Zeichen nicht findet.for (int i = 0; i < p2.size() - 1; i++) { if (p2[i] == '_' && p2[i + 1] == '_') { p2.erase(i, 1); i--; } }
Aufeinanderfolgende Unterstriche zu einem kollabieren? Ganz schön umständlich und vor allem wegen dem
i--
in deri++
-Schleife nicht leicht zu verstehen. Wie wäre stattdessen folgender Ansatz: Anstatt den String zweimal zu kopieren und den Ziel-String "zurechtzufummeln" iterierst du stattdessen einfach durch die Zeichen des Dateinamens und konstruierst währenddessen den Ziel-Dateinamen, z.B. viastd::string::append
. Dabei übersetzt du deine Zeichen und kannst auch gleich die mehrfachen Unterstriche überspringen, wenn du in der vorherigen Iteration bereits einen eingefügt hast.... for (const auto &c : p1) std::cout << static_cast<char>(c); std::cout << " -> "; for (const auto &c : p2) std::cout << static_cast<char>(c); ...
Ich habe mich gerade gefragt, was das für ne komische Ausgabe-Methode ist und nach etwas herumspielen festgestellt, dass weder
cout
- nochwcout
-Ausgabe füru16string
und auch nicht füru8string
unterstützt werden. Auch haben viele Funktionen der Standardbibliothek keine Overloads für diese Typen (WTF?) ...Die C++-Unterstützung für diese Strings scheint mir bemitleidenswert (lasse mich gerne eines besseren belehren). Ich würde daher empfehlen, auf diese zu verzichten und sich auf
std::string
/std::wstring
zu beschränken. Die Pfade kannst du so in einen (w
)string
kopieren:auto u8path = entry.path().filename().u8string(); // std::string hat keinen Konstruktor, der einen std::u8string entgegennimmt, // aber DAS geht witzigerweise: std::string path{ std::begin(u8path), std::end(u8path) }; std::cout << path << std::endl;
bzw.
auto u16path = entry.path().filename().u16string(); std::wstring path{ std::begin(u16path), std::end(u16path) }; std::wcout << path << std::endl; ...
Man kann UTF-8/16 auch durchaus in einem (
w
)string
ablegen, man muss sich nur bewusst sein, dass bei beiden Codierungen ein "Zeichen" aus mehreren Code Points (char
/char8_t
/wchar_t
/char16_t
) bestehen kann. Direkte Manipulation dieser vias[i]
sollte man daher vermeiden wenn man nicht genau weiss was man tut. Nur dass das klar ist: Auch UTF-16 ist eine Multibyte-Codierung, bei der ein "Zeichen" mehr als einen "char" haben kann.Windows und MinGW+GCC ist auch noch eine weitere unheilige Allianz, was Zeichencodierungen angeht - kein Wunder wenn du da auf Probleme stösst. Unter Linux würde das alles ein bisschen mehr zumindest "gefühlt out-of-the-box" funktionieren, da dort Konsole und System-APIs UTF-8 verwenden und man das meistens nicht einmal merkt, wenn man einfach stur
std::string
verwendet.Ich persönlich bevorzuge auch aus diesen wie auch aus Portabilitätsgründen UTF-8. Deine Dateinamen-Übersetzung würde ich daher so lösen - auch wenn man (leider) der Windows-Konsole erstmal etwas umständlich verklickern muss, dass man UTF-8 ausgeben möchte (Wichtig: Quellcode-Datei muss als UTF-8 gespeichert werden, damit das so wie hier funktioniert):
#include <iostream> #include <string> #include <regex> #if defined(_WIN32) #if !defined(WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN #endif #if !defined(NOMINMAX) #define NOMINMAX #endif #include <Windows.h> #endif auto translate(const std::string& file_name) -> std::string { static const std::regex regex{ "[^a-zA-Z0-9\\(\\)-_]+" }; return std::regex_replace(file_name, regex, "_"); } auto main() -> int { #ifdef _WIN32 auto previous_cp = GetConsoleOutputCP(); SetConsoleOutputCP(CP_UTF8); #endif std::string file_name = "צּՁぉŌ両dateinameצּՁぉŌ両"; std::cout << file_name << " -> " << translate(file_name) << std::endl; #ifdef _WIN32 SetConsoleOutputCP(previous_cp); #endif }
Demo: https://godbolt.org/z/KfbGPcddT
Falls du ausschliesslich ASCII-Zeichen erlauben willst (die alle keine Multibyte-Zeichen sind und durch nur je einen
char
repräsentiert werden), sollte dieser-Ansatz mindestens so gut sein wie eine selbstgestrickte Implementierung, auch wennstd::regex
kein UTF-8 unterstützen sollte. Du solltest nur darauf verzichten, im Regex-Ausdruck irgendwelche Nicht-ASCII-Zeichen zu verwenden, das macht dann eventuell nicht das, was du erwartest.Die Directory-Schleife in deinem Programm könnte dann z.B. so aussehen:
... for (const auto &entry : fs::directory_iterator(path)) { auto entry_path_u8string = entry.path().filename().u8string(); std::string entry_path_string{ std::begin(entry_path_u8string), std::end(entry_path_u8string) }; std::string translated_entry_path_string = translate(entry_path_string); std::cout << entry_path_string << " -> " << translated_entry_path_string << std::endl; fs::rename( entry.path(), std::u8string{ std::begin(translated_entry_path_string), std::end(translated_entry_path_string) } ); }
Beachte, dass ich hier den übersetzten Dateinamen wieder explizit in einen
std::u8string
konvertiere, damitstd::filesystem::path
auch weiss, dass der UTF-8-kodiert ist und keine komischen Sachen veranstaltet.
- ich kann es mit
-
Danke erst mal ...
Die Idee ist folgende: Alle "Sonderzeichen" durch
_
ersetzen, die im Dateinamen (ohne Dateiendung dabei) vorkommen;&
-Zeichen durchn
ersetzen, und mehrere aufeinanderfolgende_
durch genau ein_
ersetzen (also fast eine Art trim ...). Danach soll der Dateiname umbenannt werden, insofern es eine Änderung gibt.... Dann werde ich mir mal
unordered_set
bzw.regex_replace
genauer ansehen.Ja, das mit den
u16string
s ist sehr merkwürdig ... Charles Petzold hat in "Windows-Programmierung" dem ein eigenes Kapitel gewidmet, in dem es ausschließlich um die Zeichenkodierung geht. (Hab ich aber (noch) nicht gelesen)
-
Haaa, weil heute Karneval ist, und mir die Närrinnen und Narren etwas auf den Sack gehen ... mal eine etwas abwegige Frage: Weshalb hat mal die
unordered_set
eigentlichunordered_set
genannt - und nicht etwa "hash_set
", womit ich intuitiv eher gerechnet hätte? Ich hatte eigentlich nur auf dieset
(bzw.map
) "zurückgegriffen", weil ich eine "hash_set
" nicht ad hoc gefunden hatte ... Sind denn alle "Kollektionen/Behälter" nach ihrer abstrakt größtmöglichen unique Eigenschaft benannt worden - oder ist hier etwas logisch "inkosequent"? ... Oder ist das das Resultat des historisch gewachsenen Breis?
-
Haaa, weil heute Karneval ist
Was ist Karneval?
Weshalb hat mal die unordered_set eigentlich unordered_set genannt - und nicht etwa "hash_set", womit ich intuitiv eher gerechnet hätte?
Ich vermute mal, damit besser klar wird, dass eine Iteration keine garantierte Reihenfolge hat - genau wie bei der
unordered_map
. Damit kommt dann hoffentlich niemand auf die Idee, dass eine Reihenfolge vielleicht doch immer gleich ist (z.B. weil das in einer bestimmten Implementierung so sein könnte). Python hat zum Beispiel die Klassedict
- undOrderedDict
. Allerdings sind seit Version 3.6 auch normaledict
"ordered", d.h. behalten ihre Reihenfolge. Durch dasunordered
im Namen ist unmissverständlich klar, dass das nicht garantiert wird.
-
@Fragender sagte in Ein Problem mit string:
Weshalb hat mal die
unordered_set
eigentlichunordered_set
genannt - und nicht etwa "hash_set
", womit ich intuitiv eher gerechnet hätte?Ja, den Namen finde ich auch etwas verschroben. Ich hätte die beiden wahrscheinlich einfach
tree_set
undhash_set
genannt. Wie @wob schon sagte, der Name weist vermutlich darauf hin, dass ein Hash Set im Vergleich zu einem, das auf einem Suchbaum basiert, keine intrinsische Ordnung hat. Einen Suchbaum kann man via Tiefensuche abschreiten (wie es dessen Iterator z.B. vermutlich tut) und erhält damit die Elemente in der Reihenfolge ihres Ordnungskriteriums für diesen Baum.Vielleicht wollte man damit auch hervorheben, dass
unordered_set
auch für Objekte geeignet ist, die sich nicht in eine sinnvolle lineare Ordnung bringen lassen oder für die es aus anderen gründen keinenoperator<
oderstd::less
gibt. Das ist eine Voraussetzung, um einen Typ mitstd::set
verwenden zu können.
-
Ok, Danke für eure Erklärungen.
Ich bin jetzt einen ganz anderen Weg gegangen, und verwende für das File-Renaming ein Java-Programm... Das kann ich ja auch auf Linux und auf Windows ausführen... Das ist vielleicht nicht ganz Sinn der Sache, aber es funktioniert auch und es ist etwas "robuster" bzw. "resilienter", hinsichtlich der Dateinamen.
import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; public class Main { private static void dryRun(JTextArea area, boolean dry) { final String fn1 = "C:\\Users\\x\\Music\\yt-dlp\\"; final String fn2 = "D:\\yt-dlp\\"; ArrayList<String[]> map = new ArrayList<>(); File[] files = new File(fn1).listFiles(); assert files != null; for (File f : files) { String fn = f.getName(); if (fn.endsWith(".mp3")) { String prefix = fn.substring(0, fn.lastIndexOf('.')); prefix = prefix.replaceAll("&", "n"); prefix = prefix.replaceAll("[^\\-a-zA-Z0-9()_]", "_"); prefix = prefix.replaceAll("_{2,}", "_"); map.add(new String[]{f.getAbsolutePath(), fn2 + prefix + ".mp3"}); } } for (String[] sa : map) { area.append(Arrays.toString(sa) + "\n"); } area.append("\n"); if (!dry) { try { for (String[] sa : map) { if (!new File(sa[1]).exists()) { area.append(String.format("Copy %s to %s ...%n", sa[0], sa[1])); Files.copy(Path.of(sa[0]), Path.of(sa[1]), StandardCopyOption.REPLACE_EXISTING); } } area.append("Success!\n\n"); } catch (IOException ioException) { area.append("An io exception occurred!\n\n"); } } } public static void main(String[] args) { JFrame frame = new JFrame("FileCopy and FileSync 1"); JTextArea area = new JTextArea("Hallo\n\n"); JPanel panel = new JPanel(new GridLayout(1, 2)); JButton button1 = new JButton("Dry run"); JButton button2 = new JButton("Copy!"); panel.add(button1); panel.add(button2); frame.add(new JScrollPane(area)); frame.add(panel, BorderLayout.SOUTH); frame.setSize(400, 400); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setVisible(true); button1.addActionListener(e -> dryRun(area, true)); button2.addActionListener(e -> dryRun(area, false)); } }
Eigentlich ist nur Zeile 22 bis 24 interessant. Wenn ich das in C++ hinbekommen würde, wäre das super.
-
@Fragender sagte in Ein Problem mit string:
Eigentlich ist nur Zeile 22 bis 24 interessant. Wenn ich das in C++ hinbekommen würde, wäre das super.
Hab ich dir nicht das Beispiel mit
std::regex
geschrieben? Das ist die C++-Version davon. Übrigens hatte mein Regex noch ein+
am Ende, das matcht dann beliebig viele (mehr als 0) aufeinanderfolgende unzulässige Zeichen. Damit kannst du dir die Ersetzung der mehrfachen Unterstriche in der nächsten Zeile sparen, da eben beliebig viele durch nur einen Unterstrich ersetzt werden - es werden also eh keine mehrfachen Unterstriche erzeugt.Damit das funktioniert, würde ich das Regex wie folgt umschreiben:
"[^\\-a-zA-Z0-9\\(\\)]+"
(ich glaube Klammern müssen auch regex-escaped werden). Das hat das+
am Ende und der Unterstrich ist im Regex kein zulässiges Zeichen. Das hat den Effekt, dass auch bereits im Dateinamen vorhandene Unterstriche zu nur einem einzigen kollabiert werden. Also z.B.ÄÖÜ____datei
->_datei
direkt beim erstenreplaceAll
bzw.std::regex_replace
in C++.