How to define aliases with arguments? - shell

I can't retrieve the way to define a shell alias (in bash) like this one :
alias suppr='/usr/bin/find . -name "*~" | xargs rm -f'
but with "*~" as a parameter of the alias.
I would like to use it like : suppr ".bak" or suppr "*.svn" etc...
(it's just a dummy example here)

Use a function:
suppr() {
/usr/bin/find . -name "$#" | xargs rm -f
}
In general, functions are more flexible and safer to use than aliases. In fact, many people argue that functions should always be used instead of aliases.

why not save your command as a script, and put the script in Path? you can name the script file as anything.

Related

How to replace part of filename recursively in terminal / .zsh?

how can I replace a part of the filename, of a certain type (.zip), with another string, recursively through all potential nested subdirectories?
This is my filesystem structure:
dir/
|
subdir/
|
filename_strToReplace.zip
|
subdir/
|
subdir
|
filename_strToReplace.zip
filename_strToReplace.zip
filename_strToReplace.zip
So as you can see, files whose filenames need to be modiffied can be nested few levels deep. I have some moderate terminal and shell experience but not real scripting.
I believe the solution is the combination of mv, RegEx (which I can use pretty decently) and a for loop.
For what it's worth I am on a Mac, using "default" terminal (haven't messed with this) with Oh-my-zshell.
Thanks!
Using find and rename commands you can achieve that:
find . -name '*strToReplace*' | xargs -I{} rename 's/strToReplace/replacement/' {}
find search all files whose name contains strToReplace.
Then rename uses a regex to rename those files.
Use zmv:
autoload zmv
zmv -n '(dir/**/filename)_(.*).zip' '($1)_replacementStr.zip'
Remove the -n to actually perform the rename after verifying that the command will do what you want.
In bash you could achieve this using find + a custom function
#!/bin/bash
function namereplacer()
{
for file in "$#"
do
mv "$file" "${file/%stringToReplace.zip/newstring.zip}"
done
}
export -f namereplacer
find /base/path/ -depth -type f -name "*stringToReplace.zip" \
-exec bash -c 'namereplacer "$#"' _ {} +
# The 'exec {} +' form builds the command line, see find man
Note Replace /base/path with your path to base folder
I used rename similar to sjsam's answer to create a shell script. My use case was to remove .bak extension from the end of the first filename that matched the .tsx pattern:
dir=$1
extensionToChange=.bak
for file in $(find $dir -type f -name *.tsx$extensionToChange); do
echo $file
mv "$file" "${file/$extensionToChange/}"
break;
done
Had to grant execute permission on the script with chmod +x rename_first.sh
Example execution: ./rename_first.sh ../UI/test/src

Is it possible to colorize the output of gnu parallel with grep?

I have a script that runs git commands over a number of repositories in parallel which gnu parallel. I would like to pass the output of the git command through grep and color certain parts, for example on git status I want the word "clean" to appear green. Is there any way to do this with gnu parallel and grep?
This is my script so far:
#!/bin/bash
START_DIR=`pwd`
export GIT_ARGS=$*
function do_git() {
PROJECT_DIR=`dirname $1`
cd $PROJECT_DIR
echo ""
pwd
git $GIT_ARGS
echo ""
cd $START_DIR
}
export -f do_git
find . -maxdepth 2 -type d -name ".git" | sort | parallel --max-procs 4 "do_git {}"
Try adding this to the end of your pipeline:
| grep -E --color 'clean|word1|word2|$'
Substitute and add or remove words as needed. The $ causes all lines to match and pass through. The --color option is for GNU grep. Other versions of grep may use a different option.
Alternatively, there are several utilities that can do colorization.
General tips:
Avoid using all-caps variable names to prevent name collision with shell variables
Use $() instead of backticks - they're more readable and more versatile (e.g. nesting)
Using the function keyword is unnecessary
See BashFAQ/028 regarding trying to use the location of your script
I don't think GIT_ARGS need to be exported
To force grep to show colours when using parallel, try grep --color=always
I'll see if I could give a good suggestion about showing the color.
Meanwhile I think you could improve your script like this:
#!/bin/bash
function do_git {
PROJECT_DIR=${1%.git}
cd "$PROJECT_DIR"
echo
pwd
git "${#:2}"
echo
}
export -f do_git
find . -maxdepth 2 -type d -name '.git' | sort | parallel --max-procs 4 do_git '{}' "$#"
You don't have to change back with cd "$START_DIR" since it's run in a subshell (in parallel perhaps) and won't affect the calling shell.

Is there such a thing as inline bash scripts?

I want to do something on the lines of:
find -name *.mk | xargs "for i in $# do mv i i.aside end"
I realize that there might be more than on error in this, but I'd like to specifically know about this sort of inline command definition that I can pass xargs to.
This particular command isn't a great example, but you can use an "inline shell script" by giving sh -c 'here is the script' as a command. And you can give it arguments which will be $# inside the script but there's a catch: the first argument after here is the script goes to $0 inside the script, so you have to put an extra word there or you'll lose the first argument.
find . -name '*.mk' -exec sh -c 'for i; do mv "$i" "$i.aside"; done' fnord '{}' +
Another fun feature I took advantage of there is the fact that for loops iterate over the command line arguments by default: for i; do ... is equivalent to for i in "$#"; do ...
I reiterate, the above command is convoluted and slow compared to the many other methods of doing the bulk mv. I'm posting it only to show some cool syntax.
There's no need for xargs here
find -name *.mk -exec mv {} {}.aside \;
I'm not sure what the semantics of your for loop should be, but blindly coding it would give something like this:
find -name *.mk | while read file
do
for i in $file; do mv $i $i.aside; done
done
If the body is used in multiple places, you can also use bash functions.
In some version of find an argument is needed : . for the current directory
Star * must be escaped
You can try with echo command to be sure what command will do
find . -name '*.mk' -print0 | xargs -0i sh -c "echo mv '{}' '{}.aside'"
man xargs
/-i
man sh
/-c
I'm certain you could do this in a nice manner, but since you requested xargs:
find -name "*.tk" | xargs -I% mv % %.aside
Looping over filenames makes no sense, since you can only rename one at a time. Using inline uglyness is not necessary, but I could not make it work with the pipe and either eval or bash -c.

How do I get a list of all available shell commands

In a typical Linux shell (bash) it is possible to to hit tab twice, to get a list of all available shell commands.
Is there a command which has the same behaviour? I want to pipe it into grep and search it.
You could use compgen. For example:
compgen -c
You also could grep it, like this:
compgen -c | grep top$
Source: http://www.cyberciti.biz/open-source/command-line-hacks/compgen-linux-command/
You can list the directories straight from $PATH if you tweak the field separator first. The parens limit the effect to the one command, so use: (...) | grep ...
(IFS=': '; ls -1 $PATH)
"tab" twice & "y" prints all files in the paths of $PATH. So just printing all files in PATH is sufficient.
Just type this in the shell:
# printf "%s\n" ${PATH//:/\/* } > my_commands
This redirect all the commands to a file "my_commands".
List all the files in your PATH variable (ls all the directories in the PATH). The default user and system commands will be in /bin and /sbin respectively but on installing some software we will add them to some directory and link it using PATH variable.
There may be things on your path which aren't actually executable.
#!/bin/sh
for d in ${PATH//:/ }; do
for f in "$d"/*; do
test -x "$f" && echo -n "$f "
done
done
echo ""
This will also print paths, of course. If you only want unqualified filenames, it should be easy to adapt this.
Funny, StackOverflow doesn't know how to handle syntax highlighting for this. :-)
tabtaby
Similar to #ghoti, but using find:
#!/bin/sh
for d in ${PATH//:/ }; do
find $d -maxdepth 1 -type f -executable
done
Bash uses a builtin command named 'complete' to implement the tab feature.
I don't have the details to hand, but the should tell you all you need to know:
help complete
(IFS=':'; find $PATH -maxdepth 1 -type f -executable -exec basename {} \; | sort | uniq)
It doesn't include shell builtins though.
An answer got deleted, I liked it most, so I'm trying to repost it:
compgen is of course better
echo $PATH | tr ':' '\n' | xargs -n 1 ls -1
I found this to be the most typical shell thing, I think it works also with other shells (which I doubt with things like IFS=':' )
Clearly, there maybe problems, if the file is not an executable, but I think for my question, that is enough - I just want to grep my output - which means searching for some commands.

Using an alias in find -exec

I have a very long command in bash, which I do not want to type all the time, so I put an alias in my .profile
alias foo='...'
Now I want to execute this alias using find -exec
find . -exec foo '{}' \;
but find cannot find foo:
find: foo: No such file or directory
Is it possible to use an alias in find?
find itself doesn't know anything about aliases, but your shell does. If you are using a recent enough version of bash (I think 4.0 added this feature), you can use find . -exec ${BASH_ALIASES[foo]} {} \; to insert the literal content of the alias at that point in the command line.
Nope, find doesn't know anything about your aliases. Aliases are not like environment variables in that they aren't "inherited" by child processes.
You can create a shell script with the same commands, set +x permissions and have it in your path. This will work with find.
Another way of calling an alias when processing the results of find is to use something like this answer
so the following should work:
alias ll="ls -al"
find . -type d | while read folder; do ll $folder; done
I am using the ll commonly know alias for this example but you may use your alias instead, just replace ll in the following line with your alias (foo) and it should work:
find . -exec `alias ll | cut -d"'" -f2` {} \;
your case:
find . -exec `alias foo | cut -d"'" -f2` {} \;
Note it assumes your alias is quoted using the following syntax:
alias foo='your-very-long-command'
It's not possible (or difficult / error-prone) to use aliases in the find command.
An easier way to achieve the desired result is putting the contents of the alias in a shellscript and run that shellscript:
alias foo | sed "s/alias foo='//;s/'$/ \"\$#\"/" > /tmp/foo
find -exec bash /tmp/foo {} \;
The sed command removes the leading alias foo=' and replaces the trailing ' by "$#" which will contain the arguments passed to the script.
You can use the variable instead.
So instead of:
alias foo="echo test"
use:
foo="echo test"
then execute it either by command substitution or eval, for instance:
find . -type f -exec sh -c "eval $foo" \;
or:
find . -type f -exec sh -c "echo `$foo`" \;
Here is real example which is finding all non-binary files:
IS_BINARY='import sys; sys.exit(not b"\x00" in open(sys.argv[1], "rb").read())'
find . -type f -exec bash -c "python -c '$IS_BINARY' {} || echo {}" \;
I ran into the same thing and pretty much implemented skjaidev's solution.
I created a bash script called findVim.sh with the following contents:
[ roach#sepsis:~ ]$ cat findVim.sh #!/bin/bash
find . -iname $1 -exec vim '{}' \;
Then I added the the .bashrc alias as:
[ roach#sepsis:~ ]$ cat ~/.bashrc | grep fvim
alias fvim='sh ~/findVim.sh'
Finally, I reloaded .bashrc with source ~/.bashrc.
Anyways long story short I can edit arbitrary script files slightly faster with:
$ fvim foo.groovy

Resources