How to share an argument with system calls on bash? - linux

Actually I don't know much about bash programming. I've read that pipes allows we to use the output of a program as the input of another one. Then I expected that some expression like bellow to works:
echo "newdirectory" | (mkdir && cd)
Where mkdirreceives the outputed string from echo as it first argument and after cd too. The other point is that pipes not executes synchronously from left processes to the right (is that?).
There is a way to reuse an argument through the system calls on bash?
Especially in this case of creating a new directory and change to it.

You can use variables for this, and pass command line arguments to the the two commands mkdir and cd, instead of trying to pipe data to these commands.
MYDIR="newdirectory"
mkdir "$MYDIR" && cd "$MYDIR"
With this,
echo "newdirectory" | (mkdir && cd)
You connect standard input of both mkdir and cd. A program/command need to know
if it should read data from stdin, and what to do about it. Neither the mkdir or cd command does this, they expect you to give them command line arguments.
Even in the case the commands could read data from standard input, in this case mkdir would consume the input, and not leave anything for cd. In other cases where you connect the same pipe to several commands/processes, you cannot determine which one of them would read the data.
Moreover the parenthesis in (mkdir && cd) means that the commands are run in a sub-shell. However cd affects only the current shell, so you would not be able to observe any effect of the cd command.

mkdir `echo NewDirectorName`
also uses the output of a program as an argument to another program.
Another way to accomplish this is with the xargs command.
echo NewDirectoryName | xargs mkdir
#nos's answer is the most correct for your situation, though.

Related

Wrong order during eval of array content with ZSH

Considering the following example script:
#!/bin/zsh
typeset -A cmd
cmd[0]="mkdir"
cmd[1]="-p"
cmd[2]="to to/tata"
cmd[3]="anotherfolder"
I'm trying to eval the content of cmd (and keep the parameter separation, so I want to create "to to/tata" and "anotherfolder") but when I do:
${cmd}
It evals the content, but in the wrong order. For some reason it evals:
anotherfolder mkdir -p "to to/tata"
Do you have any idea why, and how to make it follow the natural order?
If you want to know what I'm really trying to do (because there might be a simpler way to do what I want), I created a small shell script that takes a command as argument, and executes it one specific folders. For exemple:
myscript mkdir -p "to to/tata"
And in my script, I simply have:
$#
That executes the mkdir command. But now, I'm trying to pass multiple command using a specific separator ("--" in my case) so that
myscript mkdir -p "to to/tata" -- touch "to to/tata/myfile"
would execute both commands. To do so, I thought it would be easier to first parse $# and create arrays containing each arguments up to "--". But now I'm stuck on how to execute the array...
The issue was that I used an associative array (typedef -A) instead of a standard array.
Proper solution would be:
cmd=()
# indexes start at 1
cmd[1]="mkdir"
cmd[2]="-p"

cd && ls | grep: How to execute a command in the current shell and pass the output

I created an alias in order not to write ls every time I move into a new directory:
alias cl='cd_(){ cd "$#" && ls; }; cd_'
Let us say I have a folder named "Downloads" (which of course I happen to have) so I just type the following in the terminal:
cl Downloads
Now I will find myself in the "Downloads" folder and receive a list of the stuff I have in the folder, like say: example.txt, hack.hs, picture.jpg,...
If I want to move to a directory and look if there is, say, hack.hs I could try something like this:
cl Downloads | grep hack
What I get is just the output:
hack.hs
But I will remain in the folder I was (which means I am not in Downloads).
I understand this happens because every command is executed in a subshell, and thus cd Downloads && ls is executed in a subshell of its own and then the output (namely the list of stuff I have) gets redirected via the pipe to grep. This is why I then am not in the new folder.
My question is the following:
How do I do it in order to be able to write something like "cl Downloads | grep hack" and get the "hack"-greped list of stuff AND be in the Downloads folder?
Thank you very much,
Pol
For anyone ever googling this:
A quick fix was proposed by #gniourf_gniourf :
cl Downloads > >(grep hack)
Some marked this question as a possible duplicate of Make bash alias that takes duplicates, but the fact that my bash alias already takes arguments shows that this is not the case. The problem at hand was about how to execute a command in the current shell while at the same time redirecting the output to another command.
As you're aware (and as is covered in BashFAQ #24), the reason
{ cd "$#" && ls; } | grep ...
...prevents the results of cd being visible in the outer shell is that no component of a pipeline is guaranteed by POSIX to be run in the outer shell. (Some shells, including ksh [out-of-the-box] and very modern bash with non-default options enabled, will occasionally or optionally run the last piece of a pipeline in the parent shell, but this can't portably be relied on).
A way to avoid this, that's applicable to all POSIX shells, is to direct output to a named pipe, thus avoiding setting up a pipeline:
mkfifo mypipe
grep ... <mypipe &
{ cd "$#" && ls; } >mypipe
In modern ksh and bash, there's a shorter syntax that will do this for you -- using /dev/fd entries instead of setting up a named pipe if the operating system provides that facility:
{ cd "$#" && ls; } > >(grep ...)
In this case, >(grep ...) is replaced with a filename that points to either a FIFO or a /dev/fd entry that, when written to by the process in question, redirects output to grep -- but without a pipeline.
By the way -- I really do hope your use of ls in this manner is as an example. The output of ls is not well-specified for the range of all possible filenames, so grepping it is innately unreliable. Consider using printf '%s\0' * to emit a NUL-delimited list of non-hidden names in a directory, if you really do want to build a streamed result; or using glob expressions to check for files matching a specific pattern (BashFAQ #4 covers a similar scenario); extglobs are available if you need something closer to full regex matching support than POSIX patterns support.

Running a script inside a loop - echo file names

I have a basic script that runs inside another script. I call mass_split.sh which then invokes split_file.sh. The splite_file.sh takes two arguments -s file_name.txt and -c 1 (how many slices to cut the file). However I trying to run a loop to find all text file names in directory ./ and then input the results to the cut_file.sh . I am getting no results back and then text files are not being split.
mass_split.sh
#!/bin/bash
for f in ./*.txt
do
sudo bash split_file.sh -s echo "file '$f'"; -c 10
done
Maybe this has something to do with that errant semicolon after the string literal, which is almost certainly not doing what you want (unless you have another executable that you're intentionally running called -c).

What does this shell script line of code mean

I need some help understanding following shell script line,
apphome = "`cd \`dirname $0\` && pwd && cd - >/dev/null`"
All I understand is, this is creating a variable called apphome.
This is not a valid shell code.
The shell don't allow spaces around =
For the rest, while this seems broken, it try to cd to the dir of the script itself, display the current dir & finally cd back to the latest cd place redirecting his standard output STDOUT to the /dev/null trash-bin (that's makes not any sense, cd display only on standard error STDERR when it fails, never on STDOUT)
If you want to do this in a proper a simple way :
apphome="$(dirname $0)"
That's all you need.
NOTE
The backquote
`
is used in the old-style command substitution, e.g.
foo=`command`
The
foo=$(command)
syntax is recommended instead. Backslash handling inside $() is less surprising, and $() is easier to nest. See http://mywiki.wooledge.org/BashFAQ/082
It seems to assign a command to the "apphome" variable. This command can be executed later.
dirname returns a directory portion of a file name. $0 is the name of the script this line contains (if I am not mistaken).
Now, executing dirname <name> will return a directory, and cd will use the value.
So, what it would do is execute three command in the row assuming that each one of them succeeds. The commands are:
cd `dirname [name of the script]`
pwd
cd -
First command will change directory to the directory containing your script; second will print current directory; third will take yo back to the original directory. Output of the third command will not be printed out.
In summary, it will print out a name of a directory containing the script that contains the line in question.
At least, this is how I understand it.

Infinite recursion aliasing `cd`

I want to record my most recent cd across any one of my terminals. I thought a good way to do this would be to write a simple bash script wrapping cd:
#!/bin/bash
cd $1 && echo `pwd` > /tmp/.cwd
Since I want the cd to occur in my terminal's process, I need to run the script with . bettercd.sh, correct?
Here comes my issue:
If I alias cd to this new . bettercd.sh, my shell also expands the cd inside of the script with the . bettercd.sh -- infinite recursion.
Is there any way to call cd from a different name but with the same behavior? To put it another way, is there some command that behaves exactly (or very similar to) cd that I can use in my shell script without noticing a difference when I use the aliased cd day to day?
My shell of choice is zsh, if that's relevant in some way.
Thanks for your help.
Since you are using zsh, you can use builtin cd in place of cd. This forces the shell to use the builtin command instead of recursively calling your alias.
builtin does not exist in standard bourne shell. If you need this to work in other shells, try prefixing cd with a backslash like this: \cd. It works to bypass aliases but not shell functions.
zsh provides the chpwd_functions hook functions specifically for tools like this. If you define a function to append the new directory to a file and add the function to the chpwd_functions array, it'll automatically run the routine every time it changed directory -- whether for pushd popd or cd:
$ record_pwd() { pwd > /tmp/cwd }
$ chpwd_functions=(record_pwd)
$ cd /tmp ; cat /tmp/cwd
/tmp
$ cd /etc ; cat /tmp/cwd
/etc
$
Inside your script call cd with builtin:
#!/bin/bash
builtin cd $1 && echo `pwd` > /tmp/.cwd
In addition to already posted answers (I personally would prefer #sarnold’s one) you can use the fact that chdir in zsh has the same behavior as cd, but is not an alias of the kind you can define with alias (it may be an “alias” in C source code, I do not know) thus using it is safe.
You could try putting unalias cd at the top of your bettercd.sh
I'd suggest a different name to avoid exactly this happening - what if some other script does a CD - if it uses your version instead of "normal", that could play havock with the system.

Resources