Bash: Nested backticks in alias cause problems - bash

I'm trying to write an alias which will jump to the descendant directory of cwd which contains a specified file (or the first find found occurrence of such a filename):
The following command combination achieves the desired result:
cd `dirname \`find -name 'MyFile.txt' | sed -n 1p\``
However, I can't seem to escape this in the correct way to create a working alias:
alias jump="cd \`dirname \\\`find -name '$1' | sed -n 1p\\\`\`"
Output:
/*
dirname: missing operand
Try `dirname --help' for more information.
bash: cd: find: No such file or directory
My logic is that backticks need escaping in a double quoted string with a single \ and I can't do \\ gets translated to a single backslash within a string, so the second nested backtick requires 1+2=3.
Any suggestions?

An alias cannot take an argument like $1. Use a function instead.
Also use $(command) for command substitution instead of backticks, as it is easier to nest.
The function would be:
jump() {
cd $(dirname $(find -name "$1" | sed -n 1p))
}

Backticks are the old form of command substitution, and you can't nest them easily. However, the new $() form does nest easily:
cd $(dirname $(find -name 'MyFile.txt' | sed -n 1p))

Backticks doesn't offer nesting. Try using command substitution which has the syntax $(..)
In your case it will be
cd $(dirname $(find /path/to/search -name 'MyFile.txt' | sed -n 1p))

Related

How to get value of {} in shell?

find . -name "*.network" -o -name "*.index" | xargs -n 1 -I {} -P 5 sh -c "ncols=3; filename={}; echo $filename"
I want get the filename stored in a variable. By setting filename={}, and echo filename, I got nothing output in console.
Since I want to use multi-thread. xargs is necessary in my script.
I use single quotes as suggested by aicastell. But I want to used awk inside quotes. What should I do with single quotes inside quotes? \s did not work.
Can anybody help me with this ?
Since $filename is within double quotes, the shell substitues it before it already before it runs your pipe. With other words: The filename in the echo statement refers to the variable in the calling shell, not in the subshell which you open with sh -c.
Hence, use single quotes instead!
You can do something like this:
for FILENAME in $(find . -name "*.network" -o -name "*.index")
do
# process $FILENAME
done

Since establishes an alias of the following command?

Hello I am trying to establish in the following alias with gitbash and cannot :
alias dirpwd='$(`pwd | xargs dirname | xargs basename -a`)' && echo -e $dirpwd;
The output is :
bash: c: command not found
Why?
Thanks all !!
What happens:
Backticks and $() work in the same way. The command inside them gets executed and the resulting output is treated as if you typed it in directly.
Assume you are in /top/c/bottom.
The part pwd | xargs dirname | xargs basename -a inside the backticks has output c.
Bash replaces the backtick part with its output, resulting in the command $(c).
Now bash tries to execute the command inside $(...) but c is not a command, hence the error bash: c: command not found.
First Fix:
I guess you just want to write
alias dirpwd="pwd | xargs dirname | xargs basename -a"
Bug:
There is a hidden bug. xargs splits at spaces and may pass multiple arguments to dirname and basename.
Example: Assume you you are in /top/a b c/. xarg creates the following command and output
dirname "/top/a" "b" "c/"
/top
.
.
Second Fix:
Use $() instead of xargs.
alias dirpwd='basename "$(dirname "$PWD")"'
Try how an solution is :
alias dirpwd="pwd | xargs dirname | xargs basename -a"

list all the files that dont match pattern in bash

i have to search the files that don't have pattern:-
*abc*.txt and *xyz*.txt
Please suggest a way to list all the files which don't have the above patterns.
You can use an extended glob, such as the following:
!(*#(abc|xyz)*.txt)
In ksh, this works by default, whereas in bash you need to first enable a shell option:
shopt -s extglob
! negates the match and # matches any of the pipe-separated patterns.
This pattern expands to the list of files that don't match *abc*.txt or *xyz*.txt, so you can pass it to another command to see the result, e.g. printf:
printf '%s\n' !(*#(abc|xyz)*.txt)
With find command:
find -type f ! \( -name '*abc*.txt' -o -name '*xyz*.txt' \)
You can use the --hide=PATTERN option with ls. In your case it would be
ls --hide="*abc*.txt" --hide="*xyz*.txt"
I got the solution simple grep is working for me
ls | grep -v "abc" | grep -v "xyz"

Edit a find -exec echo command to include a grep for a string

So I have the following command which looks for a series of files and appends three lines to the end of everything found. Works as expected.
find /directory/ -name "file.php" -type f -exec sh -c "echo -e 'string1\string2\nstring3\n' >> {}" \;
What I need to do is also look for any instance of string1, string2, or string3 in the find ouput of file.php prior to echoing/appending the lines so I don't append a file unnecessarily. (This is being run in a crontab)
Using | grep -v "string" after the find breaks the -exec command.
How would I go about accomplishing my goal?
Thanks in advance!
That -exec command isn't safe for strings with spaces.
You want something like this instead (assuming finding any of the strings is reason not to add any of the strings).
find /directory/ -name "file.php" -type f -exec sh -c "grep -q 'string1|string2|string3' \"\$1\" || echo -e 'string1\nstring2\nstring3\n' >> \"\$1\"" - {} \;
To explain the safety issue.
find places {} in the command it runs as a single argument but when you splat that into a double-quoted string you lose that benefit.
So instead of doing that you pass the file as an argument to the shell and then use the positional arguments in the shell command with quotes.
The command above simply chains the echo to a failure from grep to accomplish the goal.

why isn't this variable in a find iteration changing its value

I was trying to rename all files using find but after i ran this...
find . -name '*tablet*' -exec sh -c "new=$(echo {} | sed 's/tablet/mobile/') && mv {} $new" \;
i found that my files where gone, changed it to echo the value of $new and found that it always kept the name of the first file so it basically renamed all files to have the same name
$ find . -name '*tablet*' -exec sh -c "new=$(echo {} | sed 's/tablet/mobile/') && echo $new" \;
_prev_page.tablet.erb
_prev_page.tablet.erb
_prev_page.tablet.erb
_prev_page.tablet.erb
_prev_page.tablet.erb
_prev_page.tablet.erb
_prev_page.tablet.erb
also tried to change to export new=..., same result
Why doesn't the value of new change?
The problem I believe is that the command substitution is expanded by bash once then find uses the result in each invocation. I could be wrong with the reason.
When I have similar stuff before I write out a shell script eg
#! /bin/bash
old="$1"
new="${1/tablet/mobile}"
if [[ "${old}" != "${new}" ]]; then
mv "${old}" "${new}"
fi
that takes care of renaming the file then I can call that script from the find command
find . -name "*tablet*" -exec /path/to/script '{}' \;
makes things much simpler to sort out.
EDIT:
HAHA after some messing around with the quoting you can sort this out by changing the double quotes to single quotes encapsulating the command. As is the $() is expanded by the shell command. if done as below the command substitution is done by the shell invoked by the exec.
find . -name "*tablet*" -exec sh -c 'new=$( echo {} | sed "s/tablet/mobile/" ) && mv {} $new' \;
SO the issue is to do with when the command substitution is expanded, by puting it in single quotes we force the expansion in each invokation of sh.

Resources