how can I let my shell script with 1 argument accept default unix commands in the command line - shell

I need to write a shell script for finding the mode of the files and the code needs to be able to accept other Unix commands as optional arguments.
for example:
mycode 644 ls -l
should perform the command ls -l on all files in the current directory that have mode 644.
I only need to know what aspects of shell scripting help me here to run the ls -l.

I think for you the best will be to use find.
So for example the operation you are looking for can be executed by this simple comand:
find . -perm 0644 -exec ls -l {} \;
Your script may look like:
#!/bin/sh
perm=$1
shift
find . -perm $perm $* {} \;

The following will do it:
#!/bin/bash
mode=$1
shift
$*
It removes the first parameter into a variable, and executes the rest as a single shell command. It doesn't attempt to handle mode, but you indicated that your question wasn't about that.

Related

What is good way to move a directory and then run a command to the file inside it using a bash shell one-liner

I would like to find txt files with find command and move the directory of the found file, and then apply a command to the file using a bash shell one-liner
For example, this command works, but the acmd is executed in the current directory.
$ find . -name "*.txt" | xargs acmd
I would like to run acmd in the txt file's direcotry.
Does anyone have good idea?
From the find man page:--
-execdir command ;
-execdir command {} +
Like -exec, but the specified command is run from the subdirec‐
tory containing the matched file, which is not normally the
directory in which you started find. This a much more secure
method for invoking commands, as it avoids race conditions dur‐
ing resolution of the paths to the matched files. As with the
-exec action, the `+' form of -execdir will build a command line
to process more than one matched file, but any given invocation
of command will only list files that exist in the same subdirec‐
tory. If you use this option, you must ensure that your $PATH
environment variable does not reference `.'; otherwise, an
attacker can run any commands they like by leaving an appropri‐
ately-named file in a directory in which you will run -execdir.
The same applies to having entries in $PATH which are empty or
which are not absolute directory names. If find encounters an
error, this can sometimes cause an immediate exit, so some pend‐
ing commands may not be run at all. The result of the action
depends on whether the + or the ; variant is being used;
-execdir command {} + always returns true, while -execdir com‐
mand {} ; returns true only if command returns 0.
Just for completeness, the other option would be to do:
$ find . -name \*.txt | xargs -i sh -c 'echo "for file $(basename {}), the directory is $(dirname '{}')"'
for file schedutil.txt, the directory is ./Documentation/scheduler
for file devices.txt, the directory is ./Documentation/admin-guide
for file kernel-parameters.txt, the directory is ./Documentation/admin-guide
for file gdbmacros.txt, the directory is ./Documentation/admin-guide/kdump
...
i.e. have xargs "defer to a shell". In usecases where -execdir suffices, go for it.

How to 'cd' to the output of the 'find' command in terminal

Pretty much I want to cd to the output of the find command:
find ~ -name work_project_linux
cd the_output
In general the best way to execute an arbitrary command on the results of find is with find -exec. Curly braces {} are placeholders for the file names it finds, and the entire command ends with + or \;. For example, this will run ls -l on all of the files found:
find ~ -name work_project_linux -exec ls -l {} +
It doesn't work with some special commands like cd, though. -exec runs binaries, such as those found in /usr/bin, and cd isn't a binary. It's a shell builtin, a special type of command that the shell executes directly instead of calling out to some executable on disk. For shell builtins you can use command substitution:
cd "$(find ~ -name work_project_linux)"
This wouldn't work if find finds multiple files. It's only good for a single file name. Command substitution also won't handle some unusual file names correctly, such as those with embedded newlines—unusual, but legal.

Bash get specific file with name in all directories and run script

This is my scenario, I have a root folder called A, which contains folders B,C,D with n possibilities. Each one of those folders have a bash script. How can I get those bash scripts and run them using bash script.
A/B/run.sh
A/C/run.sh
A/D/run.sh
With find :
find . -name *.sh -type f -exec bash -c '[[ -x {} ]] || chmod u+x {}; {}' \;
For each *.sh found :
check if file has execute permissions
if not set execute permission for user
execute script
easy :)
eval "$(ls A/*/run.sh)"
ls will return a list of file with path to your scripts. (you could use find too)
the " " around the $() will make sure the result keeps new lines
eval will execute the returned lines as a script.
Mind you if there are spaces in the names of your script and stuff this can be a brittle solution. But if it looks like what you have shown, that should work fine.
this is output for an example:
~/ eval "$(ls */run.sh)"
I am running and my name is: one/run.sh
I am running and my name is: two/run.sh
the run.sh scripts are:
echo "I am running and my name is: $0"

running 2 unix commands in the same line in a batch file

Apreciate any help and excuse me if my terminology is incorrect.
What I am trying to do is write a scrpit/.bat file that will do the following:
copy 1 directory(and subdirectories) from pointA, to point B.
Then in pointB(and subdirectories) unzip the files which will give *.csv files
Then in pointB(and subdirectories) I want to delete some rows from all these csv files
This unix command, run on cygwin, will copy all the files from /cygdrive/v/pointA/* to the current directory . (i.e. the dot is the current working directory)
cp /cygdrive/v/pointA/* .
This unix command, run on cygwin, will go through all the files in the directory and subdirectories that end with .zip
and unzip them
find -iname *.zip -execdir unzip {} \;
This unix command, run on cygwin, will go through all the files in the directory and subdirectories that end with .csv
For each file it deletes the 1st 6 rows and the last row and that's the returned file.
find ./ -iname '*.csv' -exec sed -i '1,6d;$ d' '{}' ';'
I was looking to do this in one script/bat file but I am having trouble with the first find command
I am having trouble with the find and unzip commands on the one line and am wondering how and if this can be done
chdir C:\pointA
C:\cygwin\bin\cp.exe /cygdrive/v/pointB/* .
::find -iname *.zip -execdir unzip {} \;
::find ./ -iname '*.csv' -exec sed -i '1,6d;$ d' '{}' ';'
I did try something like this:
C:\cygwin\bin\find.exe -iname *.zip -execdir C:\cygwin\bin\unzip.exe {} \;
but I get the following:
/usr/bin/find: missing argument to `-execdir'
Can anyone advise if/how this can be done?
The Cygwin tools use their own kind of paths, e.g. /cygdrive/c/cygwin/bin/unzip.exe though sometimes the Windows paths with backslashes work, the backslashes do tend to confuse the Cygwin tools.
I highly recommend you write your tool in Bash shell script instead of a cmd.exe Windows batch file. In my experience (1) it's much easier to do flow control in bash scripts than in batch files, and (2) the Cygwin environment works better from Bash. You can open a bash shell and run bash yourscript.sh.
Your Bash script might look something like this: (untested)
#!/bin/bash
# This script would be run from a Cygwin Bash shell.
# You can use the Mintty program or run C:\cygwin\bin\bash --login
# to start a bash shell from Windows Command Prompt.
# Configure bash so the script will exit if a command fails.
set -e
cd /cygdrive/c/pointA
cp /cygdrive/v/pointB/* .
# I did try something like this:
# 1. Make sure you quote wildcards so the shell doesn't expand them
# before passing them to the 'find' program.
#
# 2. If you start bash with the --login option, the PATH will be
# configured so that C:\cygwin\bin is in your PATH, and you can
# just call 'find', 'cp' etc. without specifying full path to it.
# This will unzip all .zip files in all subdirectories under this one.
find -iname '*.zip' -execdir unzip {} \;

open vi with passed file name

I usually use like this
$ find -name testname.c
./dir1/dir2/testname.c
$ vi ./dir1/dir2/testname.c
it's to annoying to type file name with location again.
how can I do this with only one step?
I've tried
$ find -name testname.c | xargs vi
but I failed.
Use the -exec parameter to find.
$ find -name testname.c -exec vi {} \;
If your find returns multiple matches though, the files will be opened sequentially. That is, when you close one, it will open the next. You won't get them all queued up in buffers.
To get them all open in buffers, use:
$ vi $(find -name testname.c)
Is this really vi, by the way, and not Vim, to which vi is often aliased nowadays?
You can do it with the following commands in bash:
Either use
vi `find -name testname.c`
Or use
vi $(!!)
if you have already typed find -name testname.c
Edit: possible duplication: bash - automatically capture output of last executed command into a variable
The problem is xargs takes over all of vi's input there (and, having no other recourse, then passes on /dev/null to vi because the alternative is passing the rest of the file list), leaving no way for you to interact with it. You probably want to use a subcommand instead:
$ vi $(find -name testname.c)
Sadly there's no simple fc or r invocation that can do this for you easily after you've run the initial find, although it's easy enough to add the characters to both ends of the command after the fact.
My favorite solution is to use vim itself:
:args `find -name testname.c`
Incidentally, VIM has extended shell globbing builtin, so you can just say
:args **/testname.c
which will find recursively in the sub directory tree.
Not also, that VIM has filename completion on the commandline, so if you know you are really looking for a single file, try
:e **/test
and then press Tab (repeatedly) to cycle between any matchin filenames in the subdirectory tree.
For something a bit more robust than vi $(find -name testname.c) and the like, the following will protect against file names with whitespace and other interpreted shell characters (if you have newlines embedded in your file names, god help you). Inject this function into your shell environment:
# Find a file (or files) by name and open with vi.
function findvi()
{
declare -a fnames=()
readarray -t fnames < <(find . -name "$1" -print)
if [ "${#fnames[#]}" -gt 0 ]; then
vi "${fnames[#]}"
fi
}
Then use like
$ findvi Classname.java

Resources