【问题标题】:How to unescape backslashes on macOS?如何在 macOS 上取消转义反斜杠?
【发布时间】:2026-02-18 06:25:01
【问题描述】:

Localized.strings 文件可能包含转义字符,例如 \n\"

我如何有效地取消转义?

我知道我可以编写自己的函数来查找 \ 并将其删除,然后继续搜索下一个字符(这样我就不会将 \\ 变成空),但这不会t 处理特殊的转义方法,例如八进制字符数字(例如,\012 用于 LF)和可能的其他形式。

我认为NSString 会为此提供一个功能,但我找不到任何功能。

实际上,这似乎与NSString strip regular \ escape characters how? 重复,但该问题有很多错误答案,没有一个正确答案。并且用户不再活跃,这意味着没有人会在那里接受正确的答案。 SO应该如何处理?

【问题讨论】:

    标签: objective-c swift macos nsstring


    【解决方案1】:

    这是一个自写的 ObjC 版本,适用于大多数情况。它为 NSString 定义了一个unescaped 方法。

    #import <Cocoa/Cocoa.h>
    
    @interface NSString (BackslashUnescaping)
    - (NSString*) unescaped;
    @end
    
    @implementation NSString (BackslashUnescaping)
    
    - (NSString*) unescaped
    {
        NSString *text = self;
        NSInteger charIndex = 0;
        while (true) {
            NSInteger textLen = text.length;
            if (charIndex >= textLen) {
                break;
            }
            NSRange remainingRange = NSMakeRange (charIndex, textLen-charIndex);
            NSRange charRange = [text rangeOfString:@"\\"options:0 range:remainingRange];
            if (charRange.length == 0) {
                // no more backslashes -> done
                break;
            }
            charIndex = charRange.location + 1;
            if (charIndex >= textLen) {
                // reached end of string -> exit loop
                break;
            }
            // check char following the backslash
            unichar nextChar = [text characterAtIndex:charIndex];
            unichar replacementChar;
            NSInteger skipLen = 1;
            if (nextChar >= 'a' && nextChar <= 'z') {
                if (nextChar == 'n') {
                    replacementChar = '\n'; // LF
                } else if (nextChar == 'r') {
                    replacementChar = '\r'; // CR
                } else if (nextChar == 't') {
                    replacementChar = '\t'; // TAB
                } else if (nextChar == 'x') {
                    // A hex char code
                    const NSInteger xtraLen = 2;
                    if (charIndex+xtraLen >= textLen) break;
                    // Note: Does not make sure that both chars are valid hex chars
                    NSString *code = [text substringWithRange:NSMakeRange(charIndex+1, 2)];
                    char ch = strtol(code.UTF8String, NULL, 16);
                    replacementChar = ch;
                    skipLen += xtraLen;
                } else if (nextChar == 'u') {
                    // A unicode char code
                    const NSInteger xtraLen = 4;
                    if (charIndex+xtraLen >= textLen) break;
                    // Note: Does not make sure that all four chars are valid hex chars
                    NSString *code = [text substringWithRange:NSMakeRange(charIndex+1, 4)];
                    unichar ch = strtol(code.UTF8String, NULL, 16);
                    replacementChar = ch;
                    skipLen += xtraLen;
                } else {
                    // an unknown escape code - this should be fixed
                    NSAssert(false, @"There's a case missing for escaping \\%c", nextChar);
                }
            } else if (nextChar >= '0' && nextChar <= '9') {
                unichar nextChar2 = 0;
                if (charIndex > textLen) {  // get the second octal char
                    nextChar2 = [text characterAtIndex:charIndex+1];
                }
                if (nextChar == '0' && (nextChar2 < '0' || nextChar2 > '9')) {
                    // A short NUL (\0) char
                    replacementChar = 0;
                } else {
                    // An octal char code
                    const NSInteger xtraLen = 2;
                    if (charIndex+xtraLen >= textLen) break;
                    // Note: Does not make sure that the last char is a valid octal char
                    NSString *code = [text substringWithRange:NSMakeRange(charIndex, 3)];
                    char ch = strtol(code.UTF8String, NULL, 8); // https://*.com/a/12820646/43615
                    replacementChar = ch;
                    skipLen += xtraLen;
                }
            } else {
                // Handle all generic escapes, like for \\ and \"
                replacementChar = nextChar;
            }
            #if 0 // Use string concatenation
                charIndex += skipLen-1;
                NSString *head = [text substringToIndex:charRange.location];
                NSString *tail = [text substringFromIndex:charIndex+1];
                text = [NSString stringWithFormat:@"%@%C%@", head, replacementChar, tail];
            #else // Use a mutable string
                if (text == self) {
                    text = text.mutableCopy;
                }
                NSRange replacedRange = NSMakeRange(charRange.location, skipLen+1);
                NSString *replacement = [NSString stringWithCharacters:&replacementChar length:1];
                [(NSMutableString*)text replaceCharactersInRange:replacedRange withString:replacement];
                charIndex += 1;
            #endif 
        }
        return text;
    }
    
    @end 
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSArray *testValues = @[
                @"CR:\\rLF:\\n",
                @"\\\"quoted\\\"",
                @"Backslash: \\\\",
                @"Octal x (\170):\\170",
                @"Hex x (\x78):\\x78",
                @"Unicode Ф (\u0424):\\u0424",
                @"NUL char:\\0.",
                @"Bad Hex:\\x7x",   // this is not detected being invalid
                @"Bad Hex:\\x7",
                @"Incomplete :\\13"
            ];
            for (NSString *s in testValues) {
                NSString *s2 = [NSString stringWithFormat:@"Escaped:   %@\nUnescaped: %@", s, s.unescaped];
                printf("\n%s\n", s2.UTF8String);
            }
        }
        return 0;
    }
    

    【讨论】: