MacOS bash script not honoring double quotes - bash

I have a build script in which I'm trying to copy a file into a directory with spaces. My code works fine when the line is written as such:
cp test.png My\ Program.app/Contents/Resources
but when it's instead written as:
cp test.png “My Program.app/Contents/Resources”
...it fails with an error:
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file
cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... target_directory
What's wrong?

Adding set -x at the beginning of the script helped me see what was happening and identify the problem.
The problem was that there were spaces in the app bundle name (e.g. "My Program"), and enclosing the path in single or double quotes didn't work -- because the text editor I was using changed double quotes into smart quotes.
What also worked was escaping the spaces with a backslash, like this:
GOOD:
cp -f myfile My\ Program.app/Contents/Resources
GOOD (but be careful; some MacOS text editors may change this to the later, BAD form automatically):
cp -f myfile "My Program.app/Contents/Resources"
BAD (due to the quotes being "smart quotes" instead of plain ASCII quotes):
cp -f myfile “My Program.app/Contents/Resources”
To explain why: Because the shell only sees regular ASCII quotes as quote characters, this gets interpreted as five arguments, instead of the intended four:
cp -f myfile '“My' 'Program.app/Contents/Resources”'
# ^^ ^^ ^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# | | | | |
# 1 2 3 4 5
...and since cp only accepts more than two non-option positional arguments when the last one is a directory, but Program.app/Contents/Resources” is not a directory that actually exists, it throws a usage error.

Related

illegal option -- 1 in command for xargs shell command

I have some files which I need to rename. I tried using xargs and mv commands, but i get the error
mv: illegal option -- 1
usage: mv [-f | -i | -n] [-v] source target
mv [-f | -i | -n] [-v] source ... directory
my dir contains files like -1,-2,-3
the command I use is
ls | xargs -I{} mv '{}' old'{}'
The problem here is that the file names start with -. Most bash builtin commands treat anything preceded by - as an optional argument to the command. You can use -- to indicate the end of options for the command. For example here, -1 is taken as an argument to the mv command.
You can fix this by using
ls | xargs -I{} mv -- '{}' old'{}'
Note: You can use -t flag in xargs to see the command getting executed. Would probably make debugging easier.
From man bash
Unless otherwise noted, each builtin command documented in this section as accepting options preceded by - accepts -- to signify the end of the options.
The :, true, false, and test builtins do not accept options and do not treat -- specially. The exit, logout, break, continue, let, and shift builtins accept and process arguments beginning with - without requiring --. Other builtins that accept arguments but are not specified as accepting options interpret arguments beginning with - as invalid options and require -- to prevent this interpretation.
Note that echo does not interpret -- to mean the end of options.

Unable to pass quoted string to rm directly with xagrs

To find some file with grep and delete them with rm I tried following command -
$ ls | grep 2019 | xargs -i rm \"{}\"
That did not work. Got the following error message -
rm: cannot remove '"2019-05-10 00:00:00-TO-2019-05-10 23:59:59_PDT_disconnection_info.csv"': No such file or directory
Looks like xargs is taking quotes literally. So, tried echoing instead of passing directly -
ls | grep 2019 | xargs -i echo \"{}\" | xargs rm
This worked.
Why does not it work without echoing?
The proper quoting is done by xargs, there is no need to quote it again. Just:
... | xargs -i rm {}
Or better, because rm accepts multiple arguments, just do:
... | xargs rm
Why does not it work without echoing?
When not used with -i, -I, -d or similar, the xargs utility handles proper quoting in input with double or single quotes or escaping with a backslash. The quotes are removed by the second xargs and rm is passed unquoted string. From man xargs:
.... xargs reads
items from the standard input, delimited by blanks (which can be
protected with double or single quotes or a backslash)
Compare:
$ echo "\e\r\t\q\e" | xargs -t echo
echo ertqe
ertqe
Also see Why you shouldn't parse the output of ls(1).

The command cp -r on the terminal

How could I use cp -r command to copy more directories? For example, I'd to copy awesome.txt, neat.txt in the folder something with the command cp -r awesome.txt neat.txt something, but I have an error.
Error :
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file target_file
cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file ... target_directory
Thanks!
There are several ways you could achieve this. The easiest I have seen is to use the following.
cp /home/usr/dir/{file1,file2,file3,file4} /home/usr/destination/
The syntax uses the cp command followed by the path to the directory the desired files are located in with all the files you wish to copy wrapped in brackets and separated by commas.
Make sure to note that there are no spaces between the files. The last part of the command, /home/usr/destination/, is the directory you wish to copy the files into.
or if the all the files have the same prefix but different endings you could do something like this:
cp /home/usr/dir/file{1..4} ./
Where file1,file2,file3 and file4 would be copied.

bash copy with variable

I'm trying to copy files to the current directory using a bash script.
In order to handle paths that need escaping a variable is used that is escaped and then supplied to the cp command.
The cp command is complaining with:
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file target_file
cp [-R [-H | -L | -P]] [-fi | -n] [-apvX] source_file ... target_directory
I know what that means but I cannot understand why that happens.
Here is the code:
z="/a/b/c d (e) f.txt"
y=`printf %q "$z"`
cp $y x.txt # not working as expected
echo cp $y x.txt # output is "cp /a/b/c\ d\ \(e\)\ f.txt x.txt"
Note: When you are in trouble with a bash script, you should run it with the -x option as it provides a first level of debugging.
The escaping of the filename is incorrect. You should use:
cp "$z" x.txt
You can avoid y altogether and use quotes:
cp "$z" x.txt
This is because tokenization occurs after variable substitution. Another possibility is to change the field separator:
IFS="" # Set special variable denoting field separator (defaults to whitespace).
cp $y x.txt # Works as you intended.

How can I copy files with names containing spaces and UNICODE, when using a shell script?

I have a list of files that I'm trying to copy and move (using cp and mv) in a bash shell script. The problem that I'm running into, is that I can't get either command to recognize a huge number of files, seemingly because the filenames contain spaces and/or unicode characters. I couldn't find any switches to decode/re-encode these characters. Instead, for example, if I copy "file name.xml", I get "*.xml" and a script error that the file wasn't found for my result. Does anyone know settings or commands that will deal with these files?
EDIT(adding current code):
When I run:
MacBookPro:Desktop$ ./script.sh
#!/bin/sh
dateVar=`date +%Y-%m-%d`
mkdir /Volumes/Documents/SMSarchive/$dateVar
cd /Volumes/Documents/SMSarchive/SMSdrop
for i in *.xml
do
cp $i /Volumes/Documents/SMSarchive/$dateVar/$dateVar-$i
done
I get the message:
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-pvX] source_file target_file
cp [-R [-H | -L | -P]] [-fi | -n] [-pvX] source_file ... target_directory
...when it hits the "cp" command. There's actually more to the script, that processes the copied files further. With a "regular" file name e.g. 'file.xml', everything works fine. It's only files with spaces, or Unicode characters, where I have problems.
Problems with spaces indicates that insufficient quoting has been done. The following is incorrect:
someprogram $file
The correct version is as follows:
someprogram "$file"
watch out for code errors when $i is null!
This can be the result of fatal rm -Rf errors!
Sometimes there are easier ways than dealing with shell quoting. In your case I would use find -print0 and xargs -0. The null character termination allows you to easily manipulate strings with spaces (i.e lists of filenames).
For your example, code would look something like this:
#!/bin/sh
dateVar=`date +%Y-%m-%d`
mkdir /Volumes/Documents/SMSarchive/$dateVar
cd /Volumes/Documents/SMSarchive/SMSdrop
find . -maxdepth 1 -name '*.xml' -print0 | \
xargs -0 -I {} cp {} /Volumes/Documents/SMSarchive/$dateVar/$dateVar-{}

Resources