removing backslash with tr
Asked Answered
N

4

10

So Im removing special characters from filenames and replacing with spaces. I have all working apart from files with single backslashes contained therein. Note these files are created in the Finder on OS X

old_name="testing\this\folder"
new_name=$(echo $old_name | tr '<>:\\#%|?*' ' ');

This results in new_name being "testing hisolder" How can I just removed the backslashes and not the preceding character?

Nowicki answered 8/11, 2016 at 14:57 Comment(2)
Depending on how you define special characters, you may be able to make use of something like tr -d '[:punct:]'Stinky
This is a great suggestion -- takes away the 'escape the escape' concept and just removes slashesPassant
A
8

This results in new_name being "testing hisolder"

This string looks like the result of echo -e "testing\this\folder", because \t and \f are actually replaced with the tabulation and form feed control characters.

Maybe you have an alias like alias echo='echo -e', or maybe the implementation of echo in your version of the shell interprets backslash escapes:

POSIX does not require support for any options, and says that the behavior of ‘echo’ is implementation-defined if any STRING contains a backslash or if the first argument is ‘-n’. Portable programs can use the ‘printf’ command if they need to omit trailing newlines or output control characters or backslashes.

(from the info page)

So you should use printf instead of echo in new software. In particular, echo $old_name should be replaced with printf %s "$old_name".

There is a good explanation in this discussion, for instance.

No need for printf

As @mklement0 suggested, you can avoid the pipe by means of the Bash here string:

tr '<>:\\#%|?*' ' ' <<<"$old_name"
Abrasion answered 8/11, 2016 at 15:43 Comment(0)
L
3

Ruslan's excellent answer explains why your command may not be working for you and offers a robust, portable solution.

tl;dr:

  • You probably ran your code with sh rather than bash (even though on macOS sh is Bash in disguise), or you had shell option xpg_echo explicitly turned on.
  • Use printf instead of echo for portability.

In Bash, with the default options and using the echo builtin, your command should work as-is (except that you should double-quote $old_name for robustness), because echo by default does not expand escape sequences such as \t in its operands.

However, Bash's echo can be made to expand control-character escape sequences:

  • explicitly, by executing shopt -s xpg_echo
  • implicitly, if you run Bash as sh or with the --posix option (which, among other options and behavior changes, activates xpg_echo)

Thus, your symptom may have been caused by running your code from a script with shebang line #!/bin/sh, for instance.

However, if you're targeting sh, i.e., if you're writing a portable script, then echo should be avoided altogether for the very reason that its behavior differs across shells and platforms - see Ruslan's printf solution.


As an aside: perhaps a more robust approach to your tr command is a whitelisting approach: stating only the characters that are explicitly allowed in your result, and excluding other with the -C option:

old_name='testing\this\folder'
new_name=$(printf '%s' "$old_name" | tr -C '[:alnum:]_-' ' ')

That way, any characters that aren't either letters, numbers, _, or - are replaced with a space.

Lawford answered 8/11, 2016 at 16:27 Comment(1)
Thanks for the thorough explanation alsoNowicki
H
1

With Bash, you can use parameter expansion:

$ old_name="testing\this\folder"
$ new_name=${old_name//[<>:\\#%|?*]/ }
$ echo $new_name
testing this folder

For more, please refer to the Bash manual on shell parameter expansion.

Heritage answered 8/11, 2016 at 15:23 Comment(0)
D
-1

I think your test case is missing proper escaping for \, so you're not really testing the case of a backslash contained in a string.

This worked for me:

old_name='testing\\this\\folder'
new_name=$(echo $old_name | tr '<>:\\#%|?*' ' ');
echo $new_name
# testing this folder
Desta answered 8/11, 2016 at 15:34 Comment(1)
In Bash (which is how the question is tagged), with default options, using its echo builtin, this preserves both backslashes, and the resulting string will have 2 spaces between the words. (Conversely, the OP's code should work as-is.) Aside from that, $old_name should be double-quoted. Your escaping would only work with option xpg_echo turned on (implicitly or explicitly), but the much better solution is to use printf or a here-string.Lawford

© 2022 - 2024 — McMap. All rights reserved.