Bash to append array value to file before copying

The bash stores each uniqueid in an array and then passes them to %q to get the unique path. That seems to work what I am having trouble with is renaming each .png with the unique value in %q . I thought it was working but upon closer inspection, a .png file is being sent to scp.... but only 1 and with the wrong uniqueid . It seems like the first .png is being used by scp , but with the last uniqueid . In this example there are 2, but there may be more or less. Thank you :).

printf -v cmd_q '(cd /path/to/%q*/*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:'"$dir"'/folder/%q*-data.png)\n' "${array[@]}"
sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxx@xxx.xx.xx.xx "$cmd_q"

array

uniqueid1   ---- this is stored in array ----
data.png    ---- this is the file in the path ----

uniqueid2   ---- this is stored in array ----
data.png    ---- this is the file in the path ---

current

uniqueid1-data.png (but this is really the first png just with the wrong name)

file transfered by scp

uniqueid1-data.png
uniqueid2-data.png

This is a rather roundabout way of sending files! sshpass executing sshpass executing scp. Isn't there a better way?

Anyway, this doesn't do what you think. Each ID only gets used once. The first statement will contain the first and second ID; the second contains the third and fourth. Et cetera.

Quick fix:

for ID in "${array[@]}"
do
        echo "( cd /path/to/${ID}*/*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:'$dir'/folder/${ID}*-data.png )"
done 
# Test to make sure the output is what you want.  Then replace the above line with the below one.
# done | sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxx@xxx.xx.xx.xx
1 Like

With the echo the output is correct, but with sshpass added I get the file not foung though I can manually finnd the directory but the .png that is there is cn_results and when it is transferred by scp it is renamed or has $ID append to it. Thank you :).

echo --- looks good ---

( cd /path/to/ID1*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:path/to/destination/ID1-cn_results.png )
( cd /path/to/ID2*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:path/to/destination/ID2-cn_results.png )

echo removed and done |.... added

script.sh: line 39: ( cd /path/to/ID1*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:path/to/destination/ID1-cn_results.png ): No such file or directory
script.sh: line 39: ( cd /path/to/ID2*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:path/to/destination/ID2-cn_results.png ): No such file or directory

I tried to add the loop to scp and rename as such:

scp "$ID" xxx@xxx.xx.xx.xxx:path/to/destination/"${ID%.png}"-cn_results.png )

but I get the same result.

Show your code.

Always. Show your code.

1 Like

I tried it two ways both resulting in file or directory not found, but I can manually ssh to them. This is not the full code but I hope it's enough as I don't want to post code that is not needed. Thank you :).

attempt 1

# match unique run in list and extract samples
run=$(awk -F '\n' -v RS="" -v ref="$s5" '$0 ~ ref {d=split($0, val, " "); for(i=2;i<d;i+=2) printf "%s ",val; printf "\n"}' list)

# create array of samples from $run
readarray -t array <<< "$(printf '%s\n' $run)"
for ((i=0; i<${#array[@]}; i++ )) ; do
    echo "${array[$i]}"
done

# loop through array and append with ID
for ID in "${array[@]}" ; do
      "( cd /path/to/${ID}*/*/folder && exec sshpass -f file.txt scp xxx@xxx.xx.xx.xxx:path/to/destination/${ID}-cn_results.png )"
done | sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxxx@xxx.xx.xxx.xx

attempt 2

# match unique run in list and extract samples
run=$(awk -F '\n' -v RS="" -v ref="$s5" '$0 ~ ref {d=split($0, val, " "); for(i=2;i<d;i+=2) printf "%s ",val; printf "\n"}' list)

# create array of samples from $run
readarray -t array <<< "$(printf '%s\n' $run)"
for ((i=0; i<${#array[@]}; i++ )) ; do
    echo "${array[$i]}"
done

# loop through array and append with ID
for ID in "${array[@]}" ; do
      "( cd /path/to/${ID}*/*/folder && exec sshpass -f file.txt scp "$ID" xxx@xxx.xx.xx.xxx:path/to/destination/"${ID%}"-cn_results.png )"
done | sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxxx@xxx.xx.xxx.xx

attempt 1 error:

script.sh: line 39: ( cd /path/to/ID1*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:path/to/destination/ID1-cn_results.png ): No such file or directory
script.sh: line 39: ( cd /path/to/ID2*/*/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:path/to/destination/ID2-cn_results.png ): No such file or directory

attempt 2 error: --- in the server these exsist as ID1-xxxx-xxxxxxx-xxxxxx so I added * but thats not expanding it seems ----

ID1: No such file or directory
ID2: No such file or directory

The echo you removed was necessary. Without it, you are attempting to run a file named "( cd /path/to/${ID}*/*/folder && exec sshpass -f file.txt scp xxx@xxx.xx.xx.xxx:path/to/destination/${ID}-cn_results.png )" which quite probably does not exist.

rm -f /tmp/testoutput
for ID in "${array[@]}" ; do
      echo "( cd /path/to/${ID}*/*/folder && exec sshpass -f file.txt scp xxx@xxx.xx.xx.xxx:path/to/destination/${ID}-cn_results.png )"
done | tee /tmp/testoutput | sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxxx@xxx.xx.xxx.xx

The tee is optional but may help you by duplicating the output in /tmp/testoutput for you to check. * won't expand in /tmp/testoutput but ought to do so on the remote host.

1 Like

So if I understand and am reading the error correctly the * is not expanding on the remote machine? I think this because tee looks good but the terminal doesn't like the * . I am testing this on ubuntu but will use it on centos. Thank you :).

for ID in "${array[@]}" ; do
      echo "( cd /path/to/${ID}*/*/folder && exec sshpass -f file.txt scp xxx@xxx.xx.xx.xxx:path/to/destination/${ID}-cn_results.png )"
done | tee /tmp/tmp.txt | sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxxx@xxx.xx.xxx.xx

terminal output

-bash: line 1: cd: /path/to/ID1*/*/folder: No such file or directory
-bash: line 2: cd: /path/to/ID2*/*/folder: No such file or directory

tee ouput in tmp.txt

( cd /path/to/ID1*/*/folder && exec sshpass -f file.txt scp xxx@xxx.xx.xx.xxx:path/to/destination/ID1-cn_results.png )
( cd /path/to/ID2*/*/folder && exec sshpass -f file.txt scp xxx@xxx.xx.xx.xxx:path/to/destination/ID2-cn_results.png )

Come on - cd ing to * cannot by all means happen unless each * expands to exactly one directory.

1 Like

Each value in ID is unique and the wildcard is for the additional text after each unique value. Thank You :).

No reason * shouldn't expand, perhaps you ended up with nonprinting characters like carriage returns in your array or some such. If the commands work when pasted, that's what I'd bet.

By the way, you don't need readarray to make arrays:

# Set an array in one step in bash
array=( $(awk -F '\n' -v RS="" -v ref="$s5" '$0 ~ ref {d=split($0, val, " "); for(i=2;i<d;i+=2) printf "%s ",val; printf "\n"}' list | tr -d '\r') )
1 Like

The contents of array are and yes I can go to each directory manually, so the array is the issue? Thank you :).

declare -p array='([0]="ID1" [1]="ID2")'

I won't know the issue until it works. Nonprinting characters are nonprinting, invisible to copy-paste. If there were nonprinting characters in your array you might not see them. Did you try my code, which removes carriage returns?

1 Like

Yes i did but then i had to run and will post the output with code as soon as i can. Thank you :).

Pls post your (representative, full breadth) directory structure down to "folder". Which you should do anyhow, always.

1 Like

Directory structure:

/path/to/${ID}*/path/to/folder (always this format)

The ${ID} will always be unique and have some random text after it that the * is used for. At the last folder in the path there is a .png always named cn_results that I am trying to append the unique ${ID} on front of ${ID}-cn_results.png , then scp that .png to xxxx@xxx.xx.xxx.xx:/path/to/destination/${ID}-cn_results.png)

There are two values in array , but only the first is printed:

# Set an array in one step in bash
array=( $(awk -F '\n' -v RS="" -v ref="$s5" '$0 ~ ref {d=split($0, val, " "); for(i=2;i<d;i+=2) printf "%s ",val; printf "\n"}' list | tr -d '\r') )  ## match run with samples
echo "This is array" "$array" > /tmp/out

Contents of out

This is array ID1

I added a loop to iterate though each ID in array, but only the second value is printed:

# Set an array in one step in bash
array=( $(awk -F '\n' -v RS="" -v ref="$s5" '$0 ~ ref {d=split($0, val, " "); for(i=2;i<d;i+=2) printf "%s ",val; printf "\n"}' "$run_dir"/rna.barcodes | tr -d '\r') )  ## match run with samples
   for file in "${array[@]}" ; do
    echo "This is array" "$file" > /tmp/loop.out
done

Contents of loop.out

This is array ID2

In this example array has two values in it ID1 and ID2 , but there may be more or less it just depends on the run which the values for array come from. Thank you very much :slight_smile:

Show your actual directory structure EXPLICITLY, like

tree -d
.
├── D1
│   ├── D2
│   │   └── L1
│   ├── D3
│   │   └── L2
│   └── D4
│       ├── L1
│       ├── L2
│       └── L3
└── test
    └── D1
        └── D3
            ├── L1
            └── L2

14 directories

If your system doesn't have tree , use e.g. an editor to compose it.

1 Like

Hopefully this is better. Thank you :).

├──/path/to/
│   ├── ${ID}*   --- unique ${ID}* represents random text ---
│   │   └── /this/ID/folder
│   ├── ${ID}*   --- unique ${ID}* represents random text ---
│   │   └── /this/ID/folder 

No, it's not. How do you expect people to bug hunt if they don't see what is actually going wrong? Show the directory listing from your target system. If that is confidential, build a dummy one but adapt your code to reproduce the error.

1 Like

Hopefully, this is better and helps:

# Set run array
array=( $(awk -F '\n' -v RS="" -v ref="$s5" '$0 ~ ref {d=split($0, val, " "); for(i=2;i<d;i+=2) printf "%s ",val; printf "\n"}' list | tr -d '\r') )  ## match run in rna.barcode and extract patient

# iterate through array
for ID in "${array[*]}" ; do
echo "$ID" > /tmp/out.txt
       echo "( cd /path/to/${ID}*/path/to/folder && xec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:/path/to/destination/${ID}-cn_results)"
       sshpass -f out.txt ssh -o strictHostKeyChecking=no -t xxx@xxx.xx.xx.xx
done

Contents of out --- This is array with all samples one one line ---

ID1 ID2

echo to terminal

( cd /path/to/ID1 ID2*/*/path/to/folder && exec sshpass -f file.txt scp -- *.png* xxx@xxx.xx.xx.xxx:/path/to/destination/ID1 ID2-cn_results.png)
├──/path/to/   ---- common path after ssh ---
│   ├── ID1*   --- unique ${ID}* represents random text ---
│   │   └── /this/ID1/folder
│   ├── ID2*   --- unique ${ID}* represents random text ---
│   │   └── /this/ID2/folder
Description:

After ssh to the common path on the server, each unique ID is used to further navigate to folder. In each folder there is a png (cn_results), that the unique ID is append to (ID-cn_results) and this 
append file is scp to xxx@xxx.xx.xx.xxx:/path/to/destination. It seems that since array is one line both are being used and that is causing the error?

Well that's not going to work. You're just printing the statements to the screen and not giving anything to sshpass at all.

Does your script change the variable IFS? Strings are supposed to split when you do ARRAY=( 1 2 3 ) and not doing so is very weird unless IFS was changed.

That's why you need to be careful to do

OLDIFS="$IFS"
IFS="."

...

IFS="$OLDIFS"

...whenever you alter IFS for anything. If you don't put it back when you're done, that changes how all splitting works later.

1 Like