echo: "Why need to use \\ instead of \ to cancel the / character?" - shell

~/Desktop $ echo */*
unix/junk unix/save unix/xyxxy
I would like to cancel the slash so the shell no longer prints the files of the directories. I've found out that
~/Desktop $ echo *\/*
unix/junk unix/save unix/xyxxy
doesn't work
,while
~/Desktop $ echo *\\/*
*\/*
does the job.
Can someone explain why this is happening?

Actually, what you're trying to cancel here is not the special behavior of the \ character, but rather, the special behavior of the two * characters!
Me, I would use
echo '*/*'
or
echo "*/*"
Or, if you wanted to use \, the best way to do it would be
echo \*/\*
Actually, since there are almost certainly no files or directories named "*", you would usually be able to get away with just
echo \*/*
or
echo */\*
When you wrote
echo *\\/*
you were asking to see all the names of all the files in any subdirectory where the subdirectory name ended in a \. There probably aren't any of those, but if you want to, to see what's going on, try invoking
mkdir x\\
touch x\\/y
touch x\\/z
and then do your
echo *\\/*
again. You should see x\/y x\/z!

A single backslash is used for special sequences like for instance \n . So to insert a real - escaping - backslash, you have to write \\.

If all you want is to print the folders within your Desktop then use echo */
echo *\\/* will print anything withing a folder that ends with \
while echo *\/* will print anything withing any folder as the backslash (\) is not scaped

I didn't make myself clear with the earlier answer. What's happening with the last command
~/Desktop $ echo *\\/*
*\/*
is basically \ is escaping the next \ and *\/* is just getting printed like any other ordinary string like it happens with *something or any other string abc

Related

Using brace expansion to move files on the command line

I have a question concerning why this doesn't work. Probably, it's a simple answer, but I just can't seem to figure it out.
I want to move a couple of files I have. They all have the same filename (let's say file1) but they are all in different directories (lets say /tmp/dir1,dir2 and dir3). If I were to move these individually I could do something along the lines of:
mv /tmp/dir1/file1 /tmp
That works. However, I have multiple directories and they're all going to end up in the same spot....AND I don't want to overwrite. So, I tried something like this:
mv /tmp/{dir1,dir2,dir3}/file1 /tmp/file1.{a,b,c}
When I try this I get:
/tmp/file1.c is not a directory
Just to clarify...this also works:
mv /tmp/dir1/file1 /tmp/file1.c
Pretty sure this has to do with brace expansion but not certain why.
Thanks
Just do echo to understand how the shell expands:
$ echo mv /tmp/{dir1,dir2,dir3}/file1 /tmp/file1.{a,b,c}
mv /tmp/dir1/file1 /tmp/dir2/file1 /tmp/dir3/file1 /tmp/file1.a /tmp/file1.b /tmp/file1.c
Now you can see that your command is not what you want, because in a mv command, the destination (directory or file) is the last argument.
That's unfortunately now how the shell expansion works.
You'll have to probably use an associative array.
!/bin/bash
declare -A MAP=( [dir1]=a [dir2]=b [dir3]=c )
for ext in "${!MAP[#]}"; do
echo mv "/tmp/$ext/file1" "/tmp/file1.${MAP[$ext]}"
done
You get the following output when you run it:
mv /tmp/dir2/file1 /tmp/file1.b
mv /tmp/dir3/file1 /tmp/file1.c
mv /tmp/dir1/file1 /tmp/file1.a
Like with many other languages key ordering is not guaranteed.
${!MAP[#]} returns an array of all the keys, while ${MAP[#]} returns the an array of all the values.
Your syntax of /tmp/{dir1,dir2,dir3}/file1 expands to /tmp/dir1/file /tmp/dir2/file /tmp/dir3/file. This is similar to the way the * expansion works. The shell does not execute your command with each possible combination, it simply executes the command but expands your one value to as many as are required.
Perhaps instead of a/b/c you could differentiate them with the actual number of the dir they came from?
$: for d in 1 2 3
do echo mv /tmp/dir$d/file1 /tmp/file1.$d
done
mv /tmp/dir1/file1 /tmp/file1.1
mv /tmp/dir2/file1 /tmp/file1.2
mv /tmp/dir3/file1 /tmp/file1.3
When happy with it, take out the echo.
A relevant point - brace expansion is not a wildcard. It has nothing to do with what's on disk. It just creates strings.
So, if you create a bunch of files named with single letters or digits, echo ? will wildcard and list them all, but only the ones actually present. If there are files for vowels but not consonants, only the vowels will show. But -
if you say echo {foo,bar,nope} it will output foo bar nope regardless of whether or not any or all of those exist as files or directories, etc.

Preserve special characters in variables (use exact characters) in bash

I am trying to preserve special characters in my variables when setting them. I am trying to save file paths as variables. For example:
prompt
user input
click and drag your file here
/Users/leetbacon/Desktop/My\ Stuff/time\ to\ fly\ \&\ soar.png
You chose /Users/leetbacon/Desktop/My\ Stuff/time\ to\ fly\ \&\ soar.png
Instead, whenever I input the file it always outputs like this (which I DON'T want):
You chose /Users/leetbacon/Desktop/My Stuff/time to fly & soar.png
Any way to get it to store the variable how I would like it?
Here's the code I have right now:
echo 'click and drag your file here'
read -p " " FilepatH
echo 'You chose '"$FilepatH"
I would like for it to preserve ALL special characters. I'm just trying to write a script that can cover all possibilities of file names.
And I'm using OS X Yosemite
--Todd
I would like for it to preserve ALL special characters.
Done. In the script you posted, all characters are preserved.
You can verify that they are really preserved by running:
ls "$FilepatH"
This will work only because all special characters are preserved. If they were not preserved it wouldn't work, the file would not be found.
However, you might want to clarify the intent with the output:
echo "You chose '$FilepatH'"
This will print:
You chose '/Users/leetbacon/Desktop/My Stuff/time to fly & soar.png'
You can tell read to skip parsing (and removing) escapes and quotes by using its -r ("raw") option. But, as everyone has said, you do not want to do this. Having escapes and/or quotes embedded in the values assigned to shell variables doesn't do anything useful, because the shell does not parse them when it expands variables. See this question for an example of someone having trouble specifically because they had escapes embedded in the filenames they were trying to use.
Here's an example of doing this right:
$ cat t1.sh
#!/bin/bash
echo 'click and drag your file here'
read -p " " FilepatH
echo 'You chose '"$FilepatH"
echo
echo "Trying to use the variable with double-quotes:"
ls -l "$FilepatH"
$ ./t1.sh
click and drag your file here
/Users/gordon/weird\ chars\:\ \'\"\\\(\)\&\;.txt
You chose /Users/gordon/weird chars: '"\()&;.txt
Trying to use the variable with double-quotes:
-rw-r--r-- 1 gordon staff 0 Jul 19 22:56 /Users/gordon/weird chars: '"\()&;.txt
And here's doing it wrong (with read -r):
$ cat t2.sh
#!/bin/bash
echo 'click and drag your file here'
read -r -p " " FilepatH
echo 'You chose '"$FilepatH"
echo
echo "Trying to use the variable with double-quotes:"
ls -l "$FilepatH"
echo
echo "Trying to use the variable without double-quotes:"
ls -l $FilepatH
$ ./t2.sh
click and drag your file here
/Users/gordon/weird\ chars\:\ \'\"\\\(\)\&\;.txt
You chose /Users/gordon/weird\ chars\:\ \'\"\\\(\)\&\;.txt
Trying to use the variable with double-quotes:
ls: /Users/gordon/weird\ chars\:\ \'\"\\\(\)\&\;.txt: No such file or directory
Trying to use the variable without double-quotes:
ls: /Users/gordon/weird\: No such file or directory
ls: \'\"\\\(\)\&\;.txt: No such file or directory
ls: chars\:\: No such file or directory
Note that with the variable in double-quotes, it tried to treat the escapes as literal parts of the filename. Without them, it split the file path into separate items based on spaces, and then still treated the escapes as literal parts of the filenames.

Bash single quotes come from out of no where. How do I get rid of them?

I have the following bash script which I have been looking at to generate an example fio traffic simulation command. In the script, I take the CWD and then look for devices with a specific file name. I then append these file names with corresponding parameters onto the command to be run but when I run the command, all of the parameters are in single quotes.
What I want and need is
fio --ioengine=libaio --rw=randrw --iodepth=512 --size=10GB --name="output0" --filename="/dev/mapper/mpathfu" --name="output1" --filename="/dev/mapper/mpathfua" --name="output2" --filename="/dev/mapper/mpathfub" --name="output3" --filename="/dev/mapper/mpathfuc"
Instead what I get is .. (please take not of the single quotes.)
+ fio --ioengine=libaio --rw=randrw --iodepth=512 --size=10GB '--name="output0"' '--filename="/dev/mapper/mpathfu"' '--name="output1"' '--filename="/dev/mapper/mpathfua"' '--name="output2"' '--filename="/dev/mapper/mpathfub"' '--name="output3"' '--filename="/dev/mapper/mpathfuc"'
This is the script and I've tried lots of combinations so I apologize if there is a little bit of clutter in the script. Nothing I tried would work. How do I script this out without having to deal with the single quotes?
#fio --ioengine=libaio --rw=randrw --iodepth=512 --size=1800GB --rw=randrw --name="output1" --filename="/dev/mapper/mpathco"
cwd=$(pwd)
x=($(ls -1 ./ | grep -i mpath))
s=" "
#echo ${x[#]}
len=${#x[#]}
len=`expr $len - 1`
#echo $len
for i in `seq 0 $len`; do s=$s\ --name=\"output$i\"\ --filename=\"$cwd/${x[$i]}\"; done
echo $s
x=`echo $s`
COMMAND=fio\ --ioengine=libaio\ --rw=randrw\ --iodepth=512\ --size=10GB\ $x
echo "$COMMAND"
$COMMAND
Your problem is not the single quotes, which actually don't exist. (They appear in bash's trace output in order to show you the arguments, because the arguments include double quotes. They are not actually part of the arguments.)
The actual problem is the double quotes, which should not be in the arguments. In fact, you don't want:
fio --ioengine=libaio --rw=randrw --iodepth=512 --size=10GB \
--name="output0" --filename="/dev/mapper/mpathfu" \
--name="output1" --filename="/dev/mapper/mpathfua" \
--name="output2" --filename="/dev/mapper/mpathfub" \
--name="output3" --filename="/dev/mapper/mpathfuc"
What you want is the result of typing the above at the command-line. When you enter that line at the bash prompt, bash ends up removing the double-quotes. In the above example, the double-quotes are actually completely unnecessary. They would have been necessary had the filenames included, for example, space characters, in which case they would have served to indicate to bash that the included space characters were not argument separators. But in neither case would they be passed on to the fio command; bash always removes unquoted quote characters from arguments.
Quote characters which actually come from a bash parameter (like $COMMAND) are not unquoted quote characters. So they are not removed, and fio will try to use the filename "/dev/mapper/mpathfunc", which is an attempt to access the directory " in the current working directory. Of course, that directory doesn't exist so the command will fail.
In short, it would have worked had you written:
for i in `seq 0 $len`; do
s="$s --name=output$i --filename=$cwd/${x[$i]}
done
but only because $cwd and ${x/[$i]} don't contain spaces. What you really should do is make the accumulator an array (like x):
COMMAND=(fio --ioengine=libaio --rw=randrw --iodepth=512 --size=10GB)
len=$((${#x[#]} - 1))
for i in $(seq 0 $len); do
COMMAND+=(--name=output$i "--filename=$cwd/${x[$i]}")
done
"${COMMAND[#]}"
In Bash you can use arrays to store commands in variables
you shouldn't parse ls output.
Use $(foo) instead of `foo`.
Use More Quotes™
I got what you need, you need attach each mpath* files one by one with two option (--name and --filename) to fio command, then run it.
#! /usr/bin/bash
x=$(find $(pwd) -type f -iname "*mpath*" |awk '{printf " --name=\"output%d\" --filename=\"%s\" ", i++,$1}')
COMMAND=" fio --ioengine=libaio --rw=randrw --iodepth=512 --size=10GB $x"
echo "$COMMAND"
$COMMAND

Batch rename files using mv and sed within a for loop results in exit code 64 - How do I correct the script?

I am attempting to rename several hundred folders to remove numbers that are prefixed to the folder name. I seem to have gotten most of the way there, but my script does not quite work yet. When I echo the commands created by the script instead of running those commands, everything looks fine. The script:
for file in *
do
echo mv "'"$file"'" $(echo "'"$file"'" | sed 's/[0-9]\{1,3\} //')
done
returns many lines of:
mv '123 filename' 'filename'
mv '99 another name' 'another name'
. . . etc.
If I take one of those lines and use on the command line, the folder is renamed appropriately. If I remove the echo to actually run those mv commands, though, they do not work. Instead, mv prints out the usage reminder that comes when you incorrectly enter the command in some way.
Why do the output commands work individually but not within the for loop? How can I correct this script?
Your quoting looks oddball. The variable should be in double quotes, no more and no less.
Furthermore, you should avoid the brittle $(echo ... | sed ...); this can be accomplished in pure shell:
for f in *; do
g=${f#[0-9]}; g=${g#[0-9]}; g=${g#[0-9]}
echo mv "$f" "${g# }"
done
Run with sh -x if you want to verify that the echo gets correct quoting. Then remove the echo to actually move.
The reason the output worked when copy/pasted is that the second level of quoting was actually necessary when copying the output from echo. A (risky, convoluted) workaround would have been to replace echo with eval.

linux: Access a directory containing a space

In a script, I have this line
#!/bin/sh
log="${var}logs/console logs/since2_%m-%d-%Y.log" # <-- console logs has a space
how can I access this file?
putting quotes like:
log="${var}logs/"console logs"/since2_%m-%d-%Y.log"
cancels out the quotes around it, and escaping the quotes makes it try to find a file containing the character "
The trouble you're having is probably where you use $log, you should probably be using "$log" to preserve the spaces.
The problem is not what is quoted in the question. Here is an example script which works. Note the quotes around the USAGE of $log in addition to the definition. If you want further help, post the complete script or a minimal working subset which people can run to reproduce the problem.
#!/bin/sh
var=/tmp/
log="${var}logs/console logs/since2_%m-%d-%Y.log"
mkdir -p "$log"
rmdir "$log"
fortune | tee "$log"
echo ----
cat "$log"
the variable $IFS holds the field separator, which by default is space, so try with
oldifs="$IFS"
IFS="
"
log="${var}logs/console logs/since2_%m-%d-%Y.log"
# do whatever you want with $log now
IFS=$oldifs
If you intend to have today's date in that filename:
log="$(date "+${var}logs/console logs/since2_%m-%d-%Y.log")"
touch "$log"
I'd recommend you use %Y-%m-%d as that sorts both cronologically and lexically.
I think log="${var}logs/console\ logs/since2_%m-%d-%Y.log" should work. Try once
The idea is to escape the [SPACE]

Resources