find command in bash vs find command in cli - string

I need to accommodate spaces in filepaths. Why does "find" not work from the script, but works from the cli?
MyLaptop$ ./my-bash-script.sh
find: '/Sandbox/test folder/testfiles-good/ResidentFile_1.pcapng': No such file or directory
MyLaptop$ find '/Sandbox/test folder/testfiles-good/ResidentFile_1.pcapng'
/Sandbox/test folder/testfiles-good/ResidentFile_1.pcapng
Using echo find -f "'$line'"
output: find -f '/Sandbox/test folder/testfiles-good/ResidentFile_1.pcapng'
But in this case: FOUND="$(find -f "'$line'")"
it does not

In this case ...
FOUND="$(find -f "'$line'")"
... you are asking find for a file whose name contains leading and trailing ' characters. That is unlikely to be what you intended. Though it may look strange, this is probably what you meant:
FOUND="$(find -f "$line")"
The " characters inside the command substitution do not pair with those outside (and if they did then the original command substitution would be wrong a different way). On the other hand, word splitting is not performed on the result of the command substitution when it is used on the right-hand side of a variable assignment, so you could also just use
FOUND=$(find -f "$line")

Related

How to use case to identify a specific pattern in BASH script

I'm trying to identify several folders in my script that run from 101-121. The script is written to look through one specific folder at a time.
The error I get is:
command not found
Usage: grep [OPTION]... PATTERN [FILE]...
Try 'grep --help' for more information.
Piece of my code that is not working.
for i in 1;
do
case $i in
1)
projectfolder= `ls -l| grep "1*"` ;; #trying to identify individual folders 101-121
esac
done
does not localize the folders very well.
projectfolder= `ls -l| grep "1*"`
is a terrible thing to do. First, you probably intended to write projectfolder=$(ls -l| grep "1*") (using $() for readability, but the important detail is the lack of space after the =), but doing that is also a bad idea. Why not just do for i in 1*; do ...?
If your project folders all follow the naming pattern you describe, you should use brace expansion to expand to the numbers 101..121 and then easily iterate over them:
for projectfolder in {101..121} ; do
[ -d "$projectfolder" ] && echo "'${projectfolder}' exists and is a directory."
done
Brace expansion does not check for any of the directories' existence, so to see which one are actually there, you would test each one using [ -d.
Search for Brace Expansion in the bash(1) manual page and type help test for more information

Rename files if not the current file

I am quite new to bash scripting. I am trying to write a line which searches for all files with a particular name, and renames them, except for the currently running script.
Here is what I have so far:
find $1 -name thescript.sh -exec /bin/bash -c 'test \'"$(readlink -f "$0")" = {}\' || mv {} \$\(dirname {}\)/thescript0.sh' \; || true
Here is a brief explanation of how that works in my head:
Determine the absolute path to the currently running script using readlink
For all files {} named thescript.sh and in some subdirectory of $1:
Test if the resulting absolute path is equal to the file {}
If the test fails (i.e. they are not equal):
Determine the directory of the file {}
Move the file {} to a file under the same directory but with name thescript0.sh
Always return true
A few notes:
$1 the first argument to the script is the base directory for the renaming
Because I want to use "or" (||) within an exec clause, I followed some answers to this question and started a new shell with a quoted command within which I could use ||, so that the shell didn't think this operator is part of the parent command. The quoted command starts from test and ends at thescript0.sh
readlink is in a subshell because the result of readlink is passed to test
I used escaped quotes for the test because otherwise I thought the shell will think the first quote is actually the terminator of the previous single quote (which started the quoted command)
I also escaped the subshell, not sure if this is correct (I haven't got that far yet) because I want the subshell to be executed as part of the quoted command based on the output of find, rather than be executed at the beginning (when it won't have an output to work on yet)
If the test failed (which is expected for most cases) then I don't want the whole command to return false (not sure if it would) so that's why I put || true at the end
The error I am getting is:
line 15: unexpected EOF while looking for matching `''
I cannot find any mismatching quotes at the moment; there is only a single pair of unescaped quotes.
Single quotes don't accept any escape sequences, not even \'. To include a single quote inside a single quoted string, you have to leave and re-enter:
echo 'What'"'"'s wrong?'
echo 'Oh, it'\''s nothing.'
Additional notes:
$0 won't be expanded inside of single quotes.
find's exit code is unrelated to the results of -exec, so || true isn't needed.
For maximum safety it's best to pass {} to bash as an argument rather than embedding it in the script. That way whitespace and other special characters won't trip bash up.
Always quote variable expansions in case they contain whitespace or wildcards.
My recommended update with all of the above taken into consideration:
find "$1" -name thescript.sh -exec bash -c 'test "$1" == "$2" || mv "$2" "$(dirname "$2")"/thescript0.sh' bash "$(readlink -f "$0")" {} \;
(I haven't tested it so hopefully you get the gist even if I made some typos.)

Expanding when one has command substitution

I have to echo an output of a command substitution concatenated with string. The string to be prepended is in fact the string of pathname. The need for use of absolute path together with filename arises due to filename containing special character,-, at the beginning of it. I've come up with a draft that only works as planned for the first line of output. How do I expand it to other lines as well?
The example scenario is as provided below.
Inside /tmp directory the files are:
-foo 1.txt
-bar 1.txt
Command and the output is:
$ echo "$PWD/$(ls | grep "^-")"
/tmp/-bar 1.txt
-foo 1.txt
While I want it to be like
/tmp/-bar 1.txt
/tmp/-foo 1.txt
I read about this brace expansion feature but I'm not sure if it works for variables, or command substitution for that matter, as stated here. I also want the separate lines for each files and the filename words unsplitted, which is suggested at when the brace expansion is carried out. (Honestly, I don't understand much of the literature about the features such as brace expansion!)
Also, are there other more convenient ways to do this? Any help is appreciated.
To do what you're asking, getting a list of full paths for files starting with -, you can use readlink:
$ readlink -f ./-*
/tmp/-bar 1.pdf
/tmp/-foo 1.pdf
However, to do directly what you mentioned in comments (using filenames starting with - as arguments for pdfgrep), you can take advantage of the common convention that -- marks the end of options, so everything after it is recognized as a filename:
pdfgrep 'pattern' -- -*
See also POSIX Utility Syntax Guidelines (Guideline 10):
The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the - character.
Don't use ls at all. You can use a glob, and your loop can be implicit
$ printf '%s\n' /tmp/-*
/tmp/-foo 1.txt
/tmp/-bar 1.txt
or explicit
$ for f in /tmp/-*; do echo "$f"; done
/tmp/-foo 1.txt
/tmp/-bar 1.txt
Use this
for f in /tmp/-*
do
echo $f
done

Ubuntu Bash Script executing a command with spaces

I have a bit of an issue and i've tried several ways to fix this but i can't seem to.
So i have two shell scripts.
background.sh: This runs a given command in the background and redirect's output.
#!/bin/bash
if test -t 1; then
exec 1>/dev/null
fi
if test -t 2; then
exec 2>/dev/null
fi
"$#" &
main.sh: This file simply starts the emulator (genymotion) as a background process.
#!/bin/bash
GENY_DIR="/home/user/Documents/MyScript/watchdog/genymotion"
BK="$GENY_DIR/background.sh"
DEVICE="164e959b-0e15-443f-b1fd-26d101edb4a5"
CMD="$BK player --vm-name $DEVICE"
$CMD
This works fine when i have NO spaces in my directory. However, when i try to do: GENY_DIR="home/user/Documents/My Script/watchdog/genymotion"
which i have no choice at the moment. I get an error saying that the file or directory cannot be found. I tried to put "$CMD" in quote but it didn't work.
You can test this by trying to run anything as a background process, doesn't have to be an emulator.
Any advice or feedback would be appreciated. I also tried to do.
BK="'$BK'"
or
BK="\"$BK\""
or
BK=$( echo "$BK" | sed 's/ /\\ /g' )
Don't try to store commands in strings. Use arrays instead:
#!/bin/bash
GENY_DIR="$HOME/Documents/My Script/watchdog/genymotion"
BK="$GENY_DIR/background.sh"
DEVICE="164e959b-0e15-443f-b1fd-26d101edb4a5"
CMD=( "$BK" "player" --vm-name "$DEVICE" )
"${CMD[#]}"
Arrays properly preserve your word boundaries, so that one argument with spaces remains one argument with spaces.
Due to the way word splitting works, adding a literal backslash in front of or quotes around the space will not have a useful effect.
John1024 suggests a good source for additional reading: I'm trying to put a command in a variable, but the complex cases always fail!
try this:
GENY_DIR="home/user/Documents/My\ Script/watchdog/genymotion"
You can escape the space with a backslash.

Shell Script: Truncating String

I have two folders full of trainings and corresponding testfiles and I'd like to run the fitting pairs against each other using a shell script.
This is what I have so far:
for x in SpanishLS.train/*.train
do
timbl -f $x -t SpanishLS.test/$x.test
done
This is supposed to take file1(-n).train in one directory, look for file1(-n).test in the other, and run them trough a tool called timbl.
What it does instead is look for a file called SpanishLS.train/file1(-n).train.test which of course doesn't exist.
What I tried to do, to no avail, is truncate $x in a way that lets the script find the correct file, but whenever I do this, $x is truncated way too early, resulting in the script not even finding the .train file.
How should I code this?
If I got you right, this will do the job:
for x in SpanishLS.train/*.train
do
y=${x##*/} # strip basepath
y=${y%.*} # strip extention
timbl -f $x -t SpanishLS.test/$y.test
done
Use basename:
for x in SpanishLS.train/*.train
do
timbl -f $x -t SpanishLS.test/$(basename "$x" .train).test
done
That removes the directory prefix and the .train suffix from $x, and builds up the name you want.
In bash (and other POSIX-compliant shells), you can do the basename operation with two shell parameter expansions without invoking an external program. (I don't think there's a way to combine the two expansions into one.)
for x in SpanishLS.train/*.train
do
y=${x##*/} # Remove path prefix
timbl -f $x -t SpanishLS.test/${y%.train}.test # Remove .train suffix
done
Beware: bash supports quite a number of (useful) expansions that are not defined by POSIX. For example, ${y//.train/.test} is a bash-only notation (or bash and compatible shells notation).
Replace all occurences of .train in the filename to .text:
timbl -f $x -t $(echo $x | sed 's/\.train/.text/g')

Resources